<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>深度学习与TensorFlow | 啊粥啊周舟の部落阁</title><meta name="author" content="Zhouwy"><meta name="copyright" content="Zhouwy"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="深度学习与TensorFlow第一章 线性回归1、神经元模型首先，我们把生物神经元的模型抽象下图所示的数学结构:神经元输入向量x&#x3D;[x1, x2, x3…xn],经过函数映射: $f_{\theta}: \boldsymbol{x}$ →y后得到输出y，其中${\theta}$为函数f自身的参数。考虑一种简化的情况，即线性变换: $f(\boldsymbol{x})&#x3D;\boldsymbol{w}^">
<meta property="og:type" content="article">
<meta property="og:title" content="深度学习与TensorFlow">
<meta property="og:url" content="https://gitee.com/zwyywz/zwyywz.git/2020/04/01/DeepLearningAndTensorFlow/index.html">
<meta property="og:site_name" content="啊粥啊周舟の部落阁">
<meta property="og:description" content="深度学习与TensorFlow第一章 线性回归1、神经元模型首先，我们把生物神经元的模型抽象下图所示的数学结构:神经元输入向量x&#x3D;[x1, x2, x3…xn],经过函数映射: $f_{\theta}: \boldsymbol{x}$ →y后得到输出y，其中${\theta}$为函数f自身的参数。考虑一种简化的情况，即线性变换: $f(\boldsymbol{x})&#x3D;\boldsymbol{w}^">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/deep_learning.png">
<meta property="article:published_time" content="2020-04-01T01:25:00.000Z">
<meta property="article:modified_time" content="2023-04-16T13:03:50.103Z">
<meta property="article:author" content="Zhouwy">
<meta property="article:tag" content="机器学习">
<meta property="article:tag" content="TensorFlow">
<meta property="article:tag" content="Python">
<meta property="article:tag" content="深度学习">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/deep_learning.png"><link rel="shortcut icon" href="/zwyywz/img/favicon.png"><link rel="canonical" href="https://gitee.com/zwyywz/zwyywz.git/2020/04/01/DeepLearningAndTensorFlow/index.html"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/zwyywz/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/zwyywz/',
  algolia: undefined,
  localSearch: undefined,
  translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":300},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: true,
    post: true
  },
  runtime: '',
  dateSuffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: {"limitCount":50,"languages":{"author":"作者: Zhouwy","link":"链接: ","source":"来源: 啊粥啊周舟の部落阁","info":"著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。"}},
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false,
  percent: {
    toc: true,
    rightside: true,
  }
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: '深度学习与TensorFlow',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2023-04-16 21:03:50'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
    win.getCSS = (url,id = false) => new Promise((resolve, reject) => {
      const link = document.createElement('link')
      link.rel = 'stylesheet'
      link.href = url
      if (id) link.id = id
      link.onerror = reject
      link.onload = link.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        link.onload = link.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(link)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="///at.alicdn.com/t/c/font_4018630_wl3l75i04vl.css"><meta name="generator" content="Hexo 6.3.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="/zwyywz/img/avatar.png" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/zwyywz/archives/"><div class="headline">文章</div><div class="length-num">17</div></a><a href="/zwyywz/tags/"><div class="headline">标签</div><div class="length-num">29</div></a><a href="/zwyywz/categories/"><div class="headline">分类</div><div class="length-num">5</div></a></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/zwyywz/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/zwyywz/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/zwyywz/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 分类</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/zwyywz/note/"><i class="fa-fw iconfont icon-xuexibiji"></i><span> 学习笔记</span></a></li><li><a class="site-page child" href="/zwyywz/opensource/"><i class="fa-fw iconfont icon-xiangmu"></i><span> 开源项目</span></a></li><li><a class="site-page child" href="/zwyywz/development/"><i class="fa-fw iconfont icon-peizhi-xitongpeizhi"></i><span> 环境配置</span></a></li></ul></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 清单</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/zwyywz/music/"><i class="fa-fw fas fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/zwyywz/movies/"><i class="fa-fw fas fa-video"></i><span> 电影</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/zwyywz/link/"><i class="fa-fw fas fa-link"></i><span> 链接</span></a></div><div class="menus_item"><a class="site-page" href="/zwyywz/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg" id="page-header" style="background-image: url('https://blog-1300216920.cos.ap-nanjing.myqcloud.com/deep_learning.png')"><nav id="nav"><span id="blog-info"><a href="/zwyywz/" title="啊粥啊周舟の部落阁"><span class="site-name">啊粥啊周舟の部落阁</span></a></span><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="/zwyywz/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/zwyywz/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/zwyywz/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 分类</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/zwyywz/note/"><i class="fa-fw iconfont icon-xuexibiji"></i><span> 学习笔记</span></a></li><li><a class="site-page child" href="/zwyywz/opensource/"><i class="fa-fw iconfont icon-xiangmu"></i><span> 开源项目</span></a></li><li><a class="site-page child" href="/zwyywz/development/"><i class="fa-fw iconfont icon-peizhi-xitongpeizhi"></i><span> 环境配置</span></a></li></ul></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fas fa-list"></i><span> 清单</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/zwyywz/music/"><i class="fa-fw fas fa-music"></i><span> 音乐</span></a></li><li><a class="site-page child" href="/zwyywz/movies/"><i class="fa-fw fas fa-video"></i><span> 电影</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/zwyywz/link/"><i class="fa-fw fas fa-link"></i><span> 链接</span></a></div><div class="menus_item"><a class="site-page" href="/zwyywz/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div><div id="toggle-menu"><a class="site-page" href="javascript:void(0);"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="post-info"><h1 class="post-title">深度学习与TensorFlow</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2020-04-01T01:25:00.000Z" title="发表于 2020-04-01 09:25:00">2020-04-01</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2023-04-16T13:03:50.103Z" title="更新于 2023-04-16 21:03:50">2023-04-16</time></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="深度学习与TensorFlow"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="post-content" id="article-container"><h1 id="深度学习与TensorFlow"><a href="#深度学习与TensorFlow" class="headerlink" title="深度学习与TensorFlow"></a>深度学习与TensorFlow</h1><h2 id="第一章-线性回归"><a href="#第一章-线性回归" class="headerlink" title="第一章 线性回归"></a>第一章 线性回归</h2><h3 id="1、神经元模型"><a href="#1、神经元模型" class="headerlink" title="1、神经元模型"></a>1、神经元模型</h3><p>首先，我们把生物神经元的模型抽象下图所示的数学结构:神经元输入向量x=[x<sub>1</sub>, x<sub>2</sub>, x<sub>3</sub>…x<sub>n</sub>],经过函数映射: $f_{\theta}: \boldsymbol{x}$ →y后得到输出y，其中${\theta}$为函数f自身的参数。考虑一种简化的情况，即线性变换: $f(\boldsymbol{x})=\boldsymbol{w}^{\mathrm{T}} \boldsymbol{x}+b$， 展开为标量形式: </p>
<script type="math/tex; mode=display">f(x)=w_{1} x_{1}+w_{2} x_{2}+w_{3} x_{3}+\cdots+w_{n} x_{n}+b</script><p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210401182603937.png" alt=""></p>
<p>参数$\theta={w_1,w_2,w_3,w_4，w_4,……,w_n}$确定了神经元的状态，通过固定${\theta}$参数即可确定此神经元的处理逻辑。当神经元输入节点数n= 1(单输入)时，神经元数学模型可进一步简化为: $y=w x+b$</p>
<p>此时我们可以绘制出神经元的输出y和输入x的变化趋势，随着输入信号x的增加，输出电平y也随之线性增加，其中w参数可以理解为直线的斜率, b参数为直线的偏置。</p>
<p>对于某个神经元来说，x和y的映射关系fw.b是未知但确定的。两点即可确定一条直线，为了估计w和b的值，我们只需从直线上采样任意2个数据点:$\left(x^{(1)}, y^{(1)}\right),\left(x^{(2)}, y^{(2)}\right)$即可，其中上标表示数据点编号:</p>
<p> $y^{(1)}=w x^{(1)}+b$</p>
<p> $y^{(2)}=w x^{(2)}+b$</p>
<p>当$\left(x^{(1)}, y^{(1)}\right)\neq\left(x^{(2)}, y^{(2)}\right)$时，即可解出w,b。可以看到，只需要观测两个不同数据点，就可完美求解单输入线性神经元模型的参数，对于N输入的线性神经元模型，只需要采样N + 1组不同数据点即可，似乎线性神经元模型可以得到完美解决。那么上述方法存在什么问题呢?</p>
<p>考虑对于任何采样点，都有可能存在观测误差，我们假设观测误差变量$\epsilon$属于均值为μ,方差为σ2的正态分布(NormalDistribution， 或高斯分布，Gaussian Distibution):$\mathcal{N}\left(\mu, \sigma^{2}\right)$， 则采样到的样本符合:</p>
<script type="math/tex; mode=display">
y=w x+b+\epsilon, \epsilon \sim \mathcal{N}\left(\mu, \sigma^{2}\right)</script><p>一旦引入观测误差后，即使简单如线性模型，如果仅采样两个数据点，可能会带来较大估计偏差。图中的数据点均带有观测误差，如果基于蓝色矩形块的两个数据点进行估计，则计算出的蓝色虚线与真实橙色直线存在较大偏差。为了减少观测误差引入的估计偏差，可以通过采样多组数据样本集合：</p>
<script type="math/tex; mode=display">
D=\{\left(x^{(1)}, y^{(1)}\right),\left(x^{(2)}, y^{(2)}\right), \ldots,\left(x^{(n)}, y^{(n)}\right)\}</script><p>然后找出一条“最好”的直线，使得它尽可能地让所有采样点到该直线的误差之和最小。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210401185614687.png" alt=""></p>
<p>也就是说，由于观测误差e的存在，当我们采集了多个数据点D时，可能不存在一条直线完美的穿过所有采样点。退而求其次，我们希望能找到一条比较“好”的位于采样点中间的直线。那么怎么衡量“好”与“不好”呢?一个很自然的想法就是，求出当前模型的所有采样点上的预测值${w x^{(i)}}$ + b与真实值$y^{(i)}$之间的差的平方和作为总误差${L}$:</p>
<script type="math/tex; mode=display">
\mathcal{L}=\frac{1}{n} \sum_{i=1}^{n}\left(w x^{(i)}+b-y^{(i)}\right)^{2}</script><p>对于单输入的神经元模型，只需要两个样本，就能通过消元法求出方程组的精确解，这种通过严格的公式推导出的精确解称为解析解。这种算法固然简单直接，但是面对大规模、高维度数据的优化问题时计算效率极低，基本不可行。梯度下降算法是神经网络训练中最常用的优化算法，配合强大的图形处理芯片GPU的并行加速能力，非常适合优化海量数据的神经网络模型，自然也适合优化我们这里的神经元线性模型。</p>
<h3 id="2、梯度-Gradient-："><a href="#2、梯度-Gradient-：" class="headerlink" title="2、梯度(Gradient)："></a>2、<strong>梯度(Gradient)：</strong></h3><p>函数对各个自变量的偏导数组成的向<br>量。设函数z=f(x,y)在平面区域D内具有一阶连续偏导数，则对于每一个点P(x,y)∈D，向量$\left(\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}\right)$为函数z=f(x,y)在点P的梯度，记做<em>gradf(x,y)</em>。梯度的方向是函数在该点变化最快的方向。</p>
<hr>
<p><strong>重点：</strong>以<em>f(x,y)=-(cos<suP>2&lt;/sup&gt;x+sin<sup>2</sup>x)</em>为例；图中xy平面的红色箭头的长度表示梯度向量的模，箭头的方向表示梯度向量的方向。可以看到，箭头的方向总是指向当前位置函数值增速最大的方向，函数曲面越陡峭，箭头的长度也就越长，梯度的模也越大。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210402094531701.png" alt=""></p>
<p>函数在各处的梯度方向${\nabla}$f总是指向函数值增大的方向，那么梯度的反方向-$\nabla$f应指向函数值减少的方向。利用这一性质，只需要按照:</p>
<p>​                                                x’ = x - $\eta$·${\nabla}$f</p>
<p>来迭代更新x’，就能获得越来越小的函数值，其中n用来缩放梯度向量，一般设置为某较小的值，如0.01、 0.001 等。特别地，对于一维函数，上述向量形式可以退化成标量形式:</p>
<p>​                                                x’ = x - $\eta$·$\frac{dy}{dx}$</p>
<p>通过上式迭代更新x’若干次，这样得到的x’处的函数值y’,总是更有可能比在x处的函数值y小，优化参数的方法称为<strong>梯度下降算法</strong>。一般x表示模型输入，模型的待优化参数一 般用${\theta}$、w、b等符号表示。</p>
<hr>
<h3 id="3、线性回归-tensorflow实战"><a href="#3、线性回归-tensorflow实战" class="headerlink" title="3、线性回归-tensorflow实战"></a>3、线性回归-tensorflow实战</h3><p>首先我们需要采样自真实模型的多组数据，对于已知真实模型的玩具样例,我们直接，直接从指定的w= 1.477, b= 0.089的真实模型中直接采样:</p>
<pre><code>                                        $$y=1.477 x+0.089$$
</code></pre><p>接下来实战训练单输入神经元线性模型。</p>
<p><strong>【采样数据】</strong>:</p>
<p>为了能够很好地模拟真实样本的观测误差，我们给模型添加误差自变量$\epsilon$，它采样自均值为0，标准差为0.01的高斯分布:$y=1.477 x+0.089+\epsilon, \epsilon \sim \mathcal{N}\left(\mu, \sigma^{2}\right)$</p>
<p>通过随机采样n= 100次，我们获得n个样本的训练数据集D<sup>train</sup>，代码如下:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">data = []<span class="comment">#保存样本序列</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">100</span>):<span class="comment">#循环采集100个点</span></span><br><span class="line">  x = np.random.uniform(-<span class="number">10.</span>,<span class="number">10.</span>)<span class="comment">#随机采样输入</span></span><br><span class="line">  eps = np.random.normal(<span class="number">0.</span>,<span class="number">0.01</span>)<span class="comment">#采样高斯噪声</span></span><br><span class="line">  y = <span class="number">1.477</span> * x + <span class="number">0.089</span> + eps <span class="comment">#得到模型输出</span></span><br><span class="line">  data.append([x,y])<span class="comment">#保存样本点</span></span><br><span class="line">data = np.array(data)<span class="comment">#转化为2D numpy 数组</span></span><br></pre></td></tr></table></figure>
<p>循环进行100次采样，每次从均匀习分布$\mathcal{U}\left(-10, 10\right)$中随机采样一个数据x， 同时从均值为0，方差为0.12的高斯分布$\mathcal{N}\left(0, 0.1^{2}\right)$中随机采样噪声$\epsilon$，根据真实模型生成y的数据,并保存为Numpy数组。</p>
<p><strong>【计算误差】</strong></p>
<p>循环计算在每个点$\left(x^{(i)}, y^{(i)}\right)$处的预测值与真实值之间差的平方并累加，从而获得训练集_上的均方误差损失值。代码如下:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">mse</span>(<span class="params">b,w,points</span>):</span><br><span class="line">  <span class="comment">#根据当前的w,b计算均方差损失</span></span><br><span class="line">  totalError = <span class="number">0</span></span><br><span class="line">  <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>,<span class="built_in">len</span>(points)):</span><br><span class="line">    x = points[i, <span class="number">0</span>]</span><br><span class="line">    y = points[i ,<span class="number">1</span>]</span><br><span class="line">    totalError += (y -(w * x + b)) ** <span class="number">2</span><span class="comment">#计算差的平方，并且累加</span></span><br><span class="line">  <span class="keyword">return</span> totalError / <span class="built_in">float</span>(<span class="built_in">len</span>(points))</span><br></pre></td></tr></table></figure>
<p><strong>【计算梯度】</strong></p>
<p>根据之前介绍的梯度下降算法，我们需要计算出函数在每一个点上的梯度信息:$\left(\frac{\partial L}{\partial w}, \frac{\partial L}{\partial b}\right)$。我们来推导一下梯度的表达式，首先考虑$\frac{\partial L}{\partial w}$，将均方差函数展开:</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w}=\frac{\partial \frac{1}{n} \sum_{i=1}^{n}\left(w x^{(i)}+b-y^{(i)}\right)^{2}}{\partial w}=\frac{1}{n} \sum_{i=1}^{n} \frac{\partial\left(w x^{(i)}+b-y^{(i)}\right)^{2}}{\partial w}</script><p>考虑到：$\frac{\partial g^{2}}{\partial w}=2 \cdot g \cdot \frac{\partial g}{\partial w} $<br>         因此：</p>
<script type="math/tex; mode=display">
\begin{align}
\frac{\partial \mathcal{L}}{\partial w}
=& \frac{1}{n} \sum_{i=1}^{n} 2\left(w x^{(i)}+b-y^{(i)}\right) \cdot \frac{\partial\left(w x^{(i)}+b-y^{(i)}\right)}{\partial w}\\
=& \frac{1}{n} \sum_{i=1}^{n} 2\left(w x^{(i)}+b-y^{(i)}\right) \cdot x^{(i)} \\
=&\frac{2}{n} \sum_{i=1}^{n}\left(w x^{(i)}+b-y^{(i)}\right) \cdot x^{(i)}\\
\end{align}</script><p>同样的方法：</p>
<script type="math/tex; mode=display">
\begin{align}
\frac{\partial \mathcal{L}}{\partial b}
=& \frac{\partial \frac{1}{n} \sum_{i=1}^{n}\left(w x^{(i)}+b-y^{(i)}\right)^{2}}{\partial b}\\
=& \frac{1}{n} \sum_{i=1}^{n} \frac{\partial\left(w x^{(i)}+b-y^{(i)}\right)^{2}}{\partial b}\\
=& \frac{1}{n} \sum_{i=1}^{n} 2\left(w x^{(i)}+b-y^{(i)}\right) \cdot 1 \\
=& \frac{2}{n} \sum_{i=1}^{n}\left(w x^{(i)}+b-y^{(i)}\right) \cdot y^{(i)}\\
\end{align}</script><p>我们只需要计算在每一一个点上面的 $((w x^{(i)}+b-y^{(i)})·x^{(i)}$和$w x^{(i)}+b-y^{(i)}$值，平均后即可得到偏导数$\left(\frac{\partial L}{\partial w}, \frac{\partial L}{\partial b}\right)$。实现如下：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">step_gradient</span>(<span class="params">b_current,w_current,points,lr</span>):</span><br><span class="line">  <span class="comment">#计算误差函数所有点的导数，并且更新</span></span><br><span class="line">  b_gradient = <span class="number">0</span></span><br><span class="line">  w_gradient = <span class="number">0</span></span><br><span class="line">  m = <span class="built_in">float</span>(<span class="built_in">len</span>(points))</span><br><span class="line">  <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0</span>,<span class="built_in">len</span>(points)):</span><br><span class="line">    x = points[i, <span class="number">0</span>]</span><br><span class="line">    y = points[i ,<span class="number">1</span>]</span><br><span class="line">    <span class="comment">#计算误差函数对b的导数：grad_b = 2(wx+b-y)</span></span><br><span class="line">    b_gradient += (<span class="number">2</span>/m) * ((w_current * x + b_current) -y)</span><br><span class="line">    <span class="comment">#计算误差函数对w的导数：grad_w = 2(wx+b-y)*x</span></span><br><span class="line">    w_gradient += (<span class="number">2</span>/m) * ((w_current * x + b_current) -y) * x</span><br><span class="line"></span><br><span class="line">  <span class="comment"># 根据梯度下降算法更新 w&#x27;,b&#x27;,其中lr 为学习率</span></span><br><span class="line">  new_b = b_current - (lr * b_gradient)</span><br><span class="line">  new_w = w_current - (lr * w_gradient)</span><br><span class="line">  <span class="keyword">return</span> [new_b, new_w]</span><br></pre></td></tr></table></figure>
<p><strong>【梯度更新】</strong></p>
<p>在计算出误差函数在w和b处的梯度后，再来更新w和b的值。把对数据集的所有样本训练一次称为一个Epoch,共循环迭代num_ terations 个Epoch。实现如下:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">gradient_descent</span>(<span class="params">points, starting_b, starting_w, lr, num_iterations</span>):</span><br><span class="line">  <span class="comment"># 循环更新w,b 多次</span></span><br><span class="line">  b = starting_b <span class="comment"># b 的初始值</span></span><br><span class="line">  w = starting_w <span class="comment"># w 的初始值</span></span><br><span class="line">  <span class="comment"># 根据梯度下降算法更新多次</span></span><br><span class="line">  <span class="keyword">for</span> step <span class="keyword">in</span> <span class="built_in">range</span>(num_iterations):</span><br><span class="line">    <span class="comment"># 计算梯度并更新一次</span></span><br><span class="line">    b, w = step_gradient(b, w, np.array(points), lr)</span><br><span class="line">    loss = mse(b, w, points) <span class="comment"># 计算当前的均方差，用于监控训练进度</span></span><br><span class="line">    <span class="keyword">if</span> step%<span class="number">50</span> == <span class="number">0</span>: <span class="comment"># 打印误差和实时的w,b 值</span></span><br><span class="line">      <span class="built_in">print</span>(<span class="string">f&quot;iteration:<span class="subst">&#123;step&#125;</span>, loss:<span class="subst">&#123;loss&#125;</span>, w:<span class="subst">&#123;w&#125;</span>, b:<span class="subst">&#123;b&#125;</span>&quot;</span>)</span><br><span class="line">  <span class="keyword">return</span> [b, w] <span class="comment"># 返回最后一次的w,b</span></span><br></pre></td></tr></table></figure>
<p>主函数实现如下：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">  <span class="comment"># 加载训练集数据，这些数据是通过真实模型添加观测误差采样得到的</span></span><br><span class="line">  lr = <span class="number">0.01</span> <span class="comment"># 学习率</span></span><br><span class="line">  initial_b = <span class="number">0</span> <span class="comment"># 初始化b 为0</span></span><br><span class="line">  initial_w = <span class="number">0</span> <span class="comment"># 初始化w 为0</span></span><br><span class="line">  num_iterations = <span class="number">1000</span></span><br><span class="line">  <span class="comment"># 训练优化1000 次，返回最优w*,b*和训练Loss 的下降过程</span></span><br><span class="line">  [b, w]= gradient_descent(data, initial_b, initial_w, lr, num_iterations)</span><br><span class="line">  loss = mse(b, w, data) <span class="comment"># 计算最优数值解w,b 上的均方差</span></span><br><span class="line">  <span class="built_in">print</span>(<span class="string">f&#x27;Final loss:<span class="subst">&#123;loss&#125;</span>, w:<span class="subst">&#123;w&#125;</span>, b:<span class="subst">&#123;b&#125;</span>&#x27;</span>)</span><br></pre></td></tr></table></figure>
<p>可以看到，第100次迭代时，w和b的值就已经比较接近真实模型了，更新1000次后得到的w*和b数值解与真实模型的非常接近。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210402113941430.png" alt=""></p>
<h2 id="第二章-分类问题"><a href="#第二章-分类问题" class="headerlink" title="第二章 分类问题"></a>第二章 分类问题</h2><p>考虑图片分类中最简单的任务之一：0~9 数字图片识别，它相对简单，而且也具有非常广泛的应用价值，比如邮政编码、快递单号、手机号码等都属于数字图片识别范畴。</p>
<h3 id="2-1-手写数字图片数据集"><a href="#2-1-手写数字图片数据集" class="headerlink" title="2.1 手写数字图片数据集"></a>2.1 手写数字图片数据集</h3><p>以手写的数字图片识别为例，需要收集大量的由真人书写的0-9 的数字图片，为了便于存储和计算，一般把收集的原始图片缩放到某个固定的大小(Size 或Shape)，比如96 个像素的行和96 个像素的列(96 × 96)，这张图片将作为输入数据x。同时，我们需要给每一张图片标注一个标签，它将作为图片的真实值𝑦，这个标签表明这张图片属于哪一个具体的类别，一般通过映射方式将类别名一一对应到从0 开始编号的数字，这种编码方式叫作数字编码。对于手写数字图片识别问题，编码更为直观，我们用数字的0-9 来表示类别名字为0-9 的图片。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210405151000637.png" alt=""></p>
<p>如果希望模型能够在新样本上也能具有良好的表现，即模型泛化能力较好，那么应该尽可能多地增加数据集的规模和多样性(Variance)，使得我们用于学习的训练数据集与真实的手写数字图片的分布(Ground-truth Distribution)尽可能的逼近，这样在训练数据集上面学到了模型能够很好的用于未见过的手写数字图片的预测。</p>
<p>采用开源的MNIST数据集，这是在机器学习领域中的一个经典问题。该问题解决的是把28x28像素的灰度手写数字图片识别为相应的数字，其中数字的范围从0到9。采集自不同书写风格的真实手写图片，一共70000 张图片。其中60000 张图片作为训练集D<sup>train</sup>，用来训练模型，剩下10000 张图片作为测试集D<sup>test</sup>，这些图片由真人书写，包含了如字体大小、书写风格、粗细等丰富的样式，确保这些图片的分布与真实的手写数字图片的分布尽可能的接近，从而保证了模型的泛化能力。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210405151917695.png" alt=""></p>
<p>一张图片包含了ℎ行，𝑤列，每个位置保存了像素值，像素值一般使用0~255 的整形数值来表达颜色强度信息，例如0 表示强度最低，255 表表示强度最高。如果是彩色图片，则每个像素点包含了R、G、B 三个通道的强度信息，分别代表红色通道、绿色通道、蓝色通道的颜色强度，所以与灰度图片不同，它的每个像素点使用一个1 维、长度为3 的向量(Vector)来表示，向量的3 个元素依次代表了当前像素点上面的R、G、B 颜色强值，因此彩色图片需要保存为形状是[ℎ, 𝑤, 3]的张量(Tensor，可以通俗地理解为3 维数组)。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210405152807104.png" alt=""></p>
<p>利用TensorFlow 自动在线下载MNIST 数据集，并转换为Numpy 数组格式:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> tensorflow <span class="keyword">as</span> tf <span class="comment"># 导入TF 库</span></span><br><span class="line"><span class="keyword">from</span> tensorflow <span class="keyword">import</span> keras <span class="comment"># 导入TF 子库keras</span></span><br><span class="line"><span class="keyword">from</span> tensorflow.keras <span class="keyword">import</span> layers, optimizers, datasets <span class="comment"># 导入TF 子库等</span></span><br><span class="line">(x, y), (x_val, y_val) = datasets.mnist.load_data() <span class="comment"># 加载MNIST 数据集</span></span><br><span class="line">x = <span class="number">2</span>*tf.convert_to_tensor(x, dtype=tf.float32)/<span class="number">255.</span>-<span class="number">1</span> <span class="comment"># 转换为浮点张量，并缩放到-1~1</span></span><br><span class="line">y = tf.convert_to_tensor(y, dtype=tf.int32) <span class="comment"># 转换为整形张量</span></span><br><span class="line">y = tf.one_hot(y, depth=<span class="number">10</span>) <span class="comment"># one-hot 编码</span></span><br><span class="line"><span class="built_in">print</span>(x.shape, y.shape)</span><br><span class="line">train_dataset = tf.data.Dataset.from_tensor_slices((x, y)) <span class="comment"># 构建数据集对象</span></span><br><span class="line">train_dataset = train_dataset.batch(<span class="number">512</span>) <span class="comment"># 批量训练</span></span><br></pre></td></tr></table></figure>
<h3 id="2-2-模型构建"><a href="#2-2-模型构建" class="headerlink" title="2.2 模型构建"></a>2.2 模型构建</h3><p>对于是多输入、单输出的模型结构的话，可以使用向量形式来表示：</p>
<script type="math/tex; mode=display">
y=\boldsymbol{w}^{\mathrm{T}} \boldsymbol{x}+b=\left[w_{1}, w_{2}, w_{3}, \ldots, w_{d_{i n}}\right] \cdot\left[\begin{array}{c}
x_{1} \\
x_{2} \\
x_{3} \\
\vdots \\
x_{d_{i n}}
\end{array}\right]+b</script><p>通过组合多个多输入、单输出的神经元模型，可以拼成一个多输入、多输出的模型：𝒚 = 𝑾𝒙 + 𝒃。其中，𝒙 ∈ 𝑅<sup>𝑑<sub>in</sub></sup>，𝒃 ∈ 𝑅<sup>𝑑<sub>out</sub></sup>，𝒚 ∈ 𝑅<sup>𝑑<sub>out</sub></sup>，𝑾 ∈ 𝑅<sup>𝑑<sub>out</sub>×𝑑<sub>in</sub></sup>。</p>
<p>对于多输出节点、批量训练方式，我们将模型写成批量形式：𝒀 = 𝑿@𝑾 + 𝒃，其中，𝑿 ∈ 𝑅<sup>b*𝑑<sub>in</sub></sup>，𝒃 ∈ 𝑅<sup>𝑑<sub>out</sub></sup>，𝒚 ∈ 𝑅<sup>𝑑<sub>out</sub></sup>，𝑾 ∈ 𝑅<sup>𝑑<sub>out</sub>×𝑑<sub>in</sub></sup>。𝑑<sub>in</sub>表示输入节点数，𝑑<sub>out</sub>表示输出节点数；𝑿形状为[𝑏, 𝑑<sub>in</sub>]，表示𝑏个样本的输入数据，每个样本的特征长度为𝑑<sub>in</sub>；𝑾的形状为[𝑑<sub>in</sub>, 𝑑<sub>out</sub>]，共包含了𝑑<sub>in</sub> ∗ 𝑑<sub>out</sub>个网络参数；偏置向量𝒃 形状为𝑑<sub>out</sub>，每个输出节点上均添加一个偏置值；@符号表示矩阵相乘。</p>
<p>考虑两个样本，输入特征长度𝑑<sub>in</sub> = 3，输出特征长度𝑑<sub>out</sub> = 2的模型，展开为：</p>
<script type="math/tex; mode=display">
\left[\begin{array}{ll}
o_{1}^{(1)} & o_{2}^{(1)} 
\\
o_{1}^{(2)} & o_{2}^{(2)}
\end{array}\right]=\left[\begin{array}{lll}
x_{1}^{(1)} & x_{2}^{(1)} & x_{3}^{(1)} 
\\
x_{1}^{(2)} & x_{2}^{(2)} & x_{3}^{(2)}
\end{array}\right]\left[\begin{array}{ll}
w_{11} & w_{12}
\\
w_{21} & w_{22}
\\
w_{31} & w_{32}
\end{array}\right]+\left[\begin{array}{l}
b_{1}
\\
b_{2}
\end{array}\right]</script><p>其中𝑥1<sup>(1)</sup>、𝑜1<sup>(1)</sup>等符号的上标表示样本索引号(样本编号)，下标表示某个样本向量的元素。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210408151633039.png" alt=""></p>
<p>推广到图片，假设一张灰度图x使用矩阵存储，形状为：[h,w]。b张图片使用形状为[b,h,w]的张量X存储。而模型只接受向量形式的输入，因此需要将[h,w]打平成[h·w]的一维向量，长度d<sub>in</sub>=h·w。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210408152925228.png" alt=""></p>
<blockquote>
<p>==<strong>one-hot 编码(独热编码）：</strong>==</p>
<p>数字编码可以用一个数字来表示标签信息，此时输出只需要一个节点即可表示网络的预测类别值，例如数字1 表示猫，数字3 表示鱼等。但是数字编码一个最大的问题是，数字之间存在天然的大小关系，例如1 &lt; 2 &lt; 3，如果1、2、3 分别对应的标签是猫、狗、鱼，他们之间并没有大小关系，所以采用数字编码的时候会迫使模型去学习这种不必要的约束。</p>
<p>那么怎么解决这个问题呢？可以将输出设置为𝑑out个输出节点的向量，𝑑out与类别数相同，让第𝑖 ∈ [1, 𝑑out]个输出节点的值表示当前样本属于类别𝑖的概率𝑃(𝒙属于类别𝑖|𝒙)。我们只考虑输入图片只输入一个类别的情况，此时输入图片的真实标签已经唯一确定：如果物体属于第𝑖类的话，那么索引为𝑖的位置上设置为1，其他位置设置为0，我们把这种编码方式叫作<strong>one-hot 编码(独热编码)</strong></p>
</blockquote>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210408153228789.png" alt=""></p>
<p>手写数字图片的总类别数有10 种，即输出节点数𝑑out = 10，那么对于某个样本，假设它属于类别𝑖，即图片的中数字为𝑖，只需要一个长度为10 的向量𝒚，向量𝒚的索引号为𝑖的元素设置为1，其他位为0。比如图片0 的One-hot 编码为[1,0,0, … ,0]，图片2 的Onehot编码为[0,0,1, … ,0]，图片9 的One-hot 编码为[0,0,0, … ,1]。，相对于数字编码来说，占用较多的存储空间，所以一般在存储时还是采用数字编码，在计算时，根据需要来把数字编码转换成One-hot 编码。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">y = tf.constant([<span class="number">0</span>,<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>]) <span class="comment"># 数字编码的4 个样本标签</span></span><br><span class="line">y = tf.one_hot(y, depth=<span class="number">10</span>) <span class="comment"># one-hot 编码，指定类别总数为10</span></span><br><span class="line"><span class="built_in">print</span>(y)</span><br><span class="line"><span class="comment">###输出</span></span><br><span class="line">tf.Tensor(</span><br><span class="line">[[<span class="number">1.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span>]</span><br><span class="line"> [<span class="number">0.</span> <span class="number">1.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span>]</span><br><span class="line"> [<span class="number">0.</span> <span class="number">0.</span> <span class="number">1.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span>]</span><br><span class="line"> [<span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">1.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span> <span class="number">0.</span>]], shape=(<span class="number">4</span>, <span class="number">10</span>), dtype=float32)</span><br><span class="line"><span class="comment">###</span></span><br></pre></td></tr></table></figure>
<p>输入是一张打平后的图片向量𝒙 ∈ 𝑅<sup>784</sup>，输出是一个长度为10 的向量 ∈ 𝑅<sup>10</sup>，图片的真实标签𝑦经过One-hot 编码后变成长度为10 的非0 即1 的稀疏向量𝒚 ∈ {0,1}<sup>10</sup>。预测模型采用多输入、多输出的线性模型 = 𝑾<sup>𝐓</sup>𝒙 + 𝒃，其中模型的输出记为输入的预测值 ，我们希望 越接近真实标签𝒚越好。一般把输入经过一次(线性)变换叫作<strong>一层网络</strong>。</p>
<h3 id="2-3-误差计算"><a href="#2-3-误差计算" class="headerlink" title="2.3 误差计算"></a>2.3 误差计算</h3><p>对于𝑛个样本的均方差损失函数可以表达为：</p>
<script type="math/tex; mode=display">
\mathcal{L}(\boldsymbol{o}, \boldsymbol{y})=\frac{1}{n} \sum_{i=1}^{n} \sum_{j=1}^{10}\left(o_{j}^{(i)}-y_{j}^{(i)}\right)^{2}</script><p>现在我们只需要采用梯度下降算法来优化损失函数得到𝑾, 𝒃的最优解，然后再利用求得的模型去预测未知的手写数字图片𝒙 ∈ D<sup>test</sup>。</p>
<p>这里最起码存在两个问题：</p>
<ul>
<li>[ ] <strong>线性模型</strong> 是机器学习中间最简单的数学模型之一，参数量少，计算简单，但是只能表达线性关系。即使是简单如数字图片识别任务，它也是属于图片识别的范畴，如果只使用一个简单的线性模型去逼近复杂的人脑图片识别模型，很显然不能胜任。</li>
<li>[ ] <strong>表达能力</strong> 体现为逼近复杂分布的能力。上面的解决方案只使用了少量神经元组成的一层网络模型，相对于人脑中千亿级别的神经元互联结构，它的表达能力明显偏弱。</li>
</ul>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210408160825603.png" alt=""></p>
<h3 id="2-4-非线性模型"><a href="#2-4-非线性模型" class="headerlink" title="2.4 非线性模型"></a>2.4 非线性模型</h3><p>既然线性模型不可行，我们可以给线性模型嵌套一个非线性函数，即可将其转换为非线性模型。我们把这个非线性函数称为激活函数(Activation Function)，用𝜎表示：o= 𝜎(𝑾𝒙 + 𝒃)，这里的𝜎代表了某个具体的非线性激活函数，如Sigmoid 函数、ReLU 函数。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210408161820924.png" alt=""></p>
<p>ReLU 函数非常简单，在𝑦 = 𝑥的基础上面截去了𝑥 &lt; 0的部分，可以直观地理解为ReLU 函数仅保留正的输入部份，清零负的输入，具有单边抑制特性。ReLU 函数却有优良的非线性特性，而且梯度计算简单，训练稳定，是深度学习模型使用最广泛的激活函数之一。我们这里通过嵌套ReLU 函数将模型转换为非线性模型：o= ReLU(𝑾𝒙 + 𝒃)</p>
<h3 id="2-5-表达能力"><a href="#2-5-表达能力" class="headerlink" title="2.5 表达能力"></a>2.5 表达能力</h3><p>针对于模型的表达能力偏弱的问题，可以通过重复堆叠多次变换来增加其表达能力：</p>
<script type="math/tex; mode=display">
\begin{align}
h1 =& ReLU(W_1X+b1)\\
h2 =& ReLU(W_2X+b2)\\
o =& W_3h2+b_3
 \end{align}</script><p>把第一层神经元的输出值𝒉1作为第二层神经元模型的输入，把第二层神经元的输出𝒉2作为第三层神经元的输入，最后一层神经元的输出作为模型的输出 。函数的嵌套表现为网络层的前后相连，每堆叠一个(非)线性环节，网络层数增加一层。我们把输入节点𝒙所在的层叫作输入层，每一个非线性模块的输出𝒉𝑖连同它的网络层参数𝑾𝑖和𝒃𝑖称为一层网络层，特别地，对于网络中间的层，叫作隐藏层，最后一层叫作输出层。这种由大量神经元模型连接形成的网络结构称为神经网络(Neural Network)。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210408163123638.png" alt=""></p>
<h3 id="2-6-手写数字图片识别-Tensorflow实战"><a href="#2-6-手写数字图片识别-Tensorflow实战" class="headerlink" title="2.6 手写数字图片识别-Tensorflow实战"></a>2.6 手写数字图片识别-Tensorflow实战</h3><p>对于第一层模型来说，它接受的输入𝒙 ∈ 𝑅<sup>784</sup>，输出𝒉1 ∈ 𝑅<sup>56</sup>设计为长度为256 的向量，不需要显式地编写$𝒉1 = ReLU(𝑾_1𝒙 + 𝒃_1)$的计算逻辑，在TensorFlow 中通过一行代码即可实现：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 创建一层网络，设置输出节点数为256，激活函数类型为ReLU</span></span><br><span class="line">layers.Dense(<span class="number">256</span>, activation=<span class="string">&#x27;relu&#x27;</span>)</span><br></pre></td></tr></table></figure>
<p>使用TensorFlow 的Sequential 容器可以非常方便地搭建多层的网络。对于3 层网络，我们可以通过快速完成3 层网络的搭建：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 利用Sequential 容器封装3 个网络层，前网络层的输出默认作为下一层的输入</span></span><br><span class="line">model = keras.Sequential([ <span class="comment"># 3 个非线性层的嵌套模型</span></span><br><span class="line">layers.Dense(<span class="number">256</span>, activation=<span class="string">&#x27;relu&#x27;</span>), <span class="comment"># 隐藏层1</span></span><br><span class="line">layers.Dense(<span class="number">128</span>, activation=<span class="string">&#x27;relu&#x27;</span>), <span class="comment"># 隐藏层2</span></span><br><span class="line">layers.Dense(<span class="number">10</span>)]) <span class="comment"># 输出层，输出节点数为10</span></span><br></pre></td></tr></table></figure>
<p>第1 层的输出节点数设计为256，第2 层设计为128，输出层节点数设计为10。直接调用这个模型对象model(x)就可以返回模型最后一层的输出𝑜。</p>
<p>搭建完成3 层神经网络的对象后，给定输入𝒙，调用model(𝒙)得到模型输出𝑜后，通过MSE 损失函数计算当前的误差ℒ：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">with</span> tf.GradientTape() <span class="keyword">as</span> tape: <span class="comment"># 构建梯度记录环境</span></span><br><span class="line">    <span class="comment"># 打平操作，[b, 28, 28] =&gt; [b, 784]</span></span><br><span class="line">    x = tf.reshape(x, (-<span class="number">1</span>, <span class="number">28</span>*<span class="number">28</span>))</span><br><span class="line">    <span class="comment"># Step1. 得到模型输出output [b, 784] =&gt; [b, 10]</span></span><br><span class="line">    out = model(x)</span><br><span class="line">    <span class="comment"># [b] =&gt; [b, 10]</span></span><br><span class="line">    y_onehot = tf.one_hot(y, depth=<span class="number">10</span>)</span><br><span class="line">    <span class="comment"># 计算差的平方和，[b, 10]</span></span><br><span class="line">    loss = tf.square(out-y_onehot)</span><br><span class="line">    <span class="comment"># 计算每个样本的平均误差，[b]</span></span><br><span class="line">    loss = tf.reduce_sum(loss) / x.shape[<span class="number">0</span>]</span><br></pre></td></tr></table></figure>
<p>再利用TensorFlow 提供的自动求导函数tape.gradient(loss, model.trainable_variables)求出模型中所有参数的梯度信息$\frac{\partial L}{\partial 𝜃}$, 𝜃 ∈ {𝑾1, 𝒃1, 𝑾2, 𝒃2, 𝑾3, 𝒃3}。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Step3. 计算参数的梯度 w1, w2, w3, b1, b2, b3</span></span><br><span class="line">grads = tape.gradient(loss, model.trainable_variables)</span><br></pre></td></tr></table></figure>
<p>计算获得的梯度结果使用grads 列表变量保存。再使用optimizers 对象自动按照梯度更新法则去更新模型的参数𝜃。</p>
<script type="math/tex; mode=display">𝜃' = 𝜃 - \eta·\frac{\partial L}{\partial 𝜃}</script><p>实现如下:</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 自动计算梯度</span></span><br><span class="line">grads = tape.gradient(loss, model.trainable_variables)</span><br><span class="line"><span class="comment"># w&#x27; = w - lr * grad，更新网络参数</span></span><br><span class="line">optimizer.apply_gradients(<span class="built_in">zip</span>(grads, model.trainable_variables))</span><br></pre></td></tr></table></figure>
<p>由于3 层的神经网络表达能力较强，手写数字图片识别任务相对简单，误差值可以较快速、稳定地下降，其中，把对数据集的所有样本迭代一遍叫作一个Epoch，我们可以在间隔数个Epoch 后测试模型的准确率等指标，方便监控模型的训练效果。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210408180132023.png" alt=""></p>
<h2 id="第三章-TensorFlow编程基础"><a href="#第三章-TensorFlow编程基础" class="headerlink" title="第三章 TensorFlow编程基础"></a>第三章 TensorFlow编程基础</h2><h3 id="3-1-数据类型"><a href="#3-1-数据类型" class="headerlink" title="3.1 数据类型"></a>3.1 数据类型</h3><p>1、<strong>数值类型</strong>：张量[Tensor]是[TensorFlow] 的主要数据载体，根据维度数来区分，可分为：</p>
<ul>
<li><p>标量(Scalar)。单个的实数，如1.2, 3.4 等，维度(Dimension)数为0，shape 为[]。</p>
</li>
<li><p>向量(Vector)。𝑛个实数的有序集合，通过中括号包裹，如[1.2]，[1.2, 3.4]等，维度数<br>为1，长度不定，shape 为[𝑛]。</p>
</li>
<li><p>矩阵(Matrix)。。𝑛行𝑚列实数的有序集合，如[[1,2], [3,4]]，也可以写成$\left[ \begin{matrix} 1 &amp; 2\ 3 &amp; 4 \end{matrix} \right] $</p>
</li>
<li><p>张量(Tensor)。所有维度数dim &gt; 2的数组统称为张量。张量的每个维度也作轴(Axis)，一般维度代表了具体的物理含义，比如Shape 为[2,32,32,3]的张量共有4 维，如果表示图片数据的话，每个维度/轴代表的含义分别是图片数量、图片高度、图片宽度、图片通道数，其中2 代表了2 张图片，32 代表了高、宽均为32，3 代表了RGB 共3 个通道。张量的维度数以及每个维度所代表的具体物理含义需要由用户自行定义。</p>
</li>
</ul>
<p>首先来看标量在TensorFlow 是如何创建的，实现如下：</p>
   <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">b = tf.constant([<span class="number">1</span>,<span class="number">2.</span>,<span class="number">1</span>,<span class="number">9</span>])</span><br><span class="line"><span class="built_in">print</span>(b)</span><br><span class="line"><span class="comment">#tf.Tensor([1. 2. 1. 9.], shape=(4,), dtype=float32)</span></span><br><span class="line"><span class="built_in">print</span>(x.numpy())</span><br><span class="line"><span class="comment"># 将TF 张量的数据导出为numpy 数组格式</span></span><br></pre></td></tr></table></figure>
<p>同样的方法，定义矩阵的实现如下：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">a = tf.constant([[<span class="number">1</span>,<span class="number">2</span>],[<span class="number">3</span>,<span class="number">4</span>]])</span><br><span class="line"><span class="built_in">print</span>(a)</span><br><span class="line"><span class="built_in">print</span>(a.shape)</span><br><span class="line"><span class="comment">#tf.Tensor([</span></span><br><span class="line"><span class="comment">#[1 2]</span></span><br><span class="line"><span class="comment"># [3 4]]</span></span><br><span class="line"><span class="comment"># shape=(2, 2), dtype=int32) </span></span><br><span class="line"><span class="comment">#(2, 2)</span></span><br></pre></td></tr></table></figure>
<p>2、<strong>字符串类型</strong>：除了丰富的数值类型张量外，TensorFlow 还支持字符串(String) 类型的数据。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">a = tf.constant(<span class="string">&quot;hello world&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(a)</span><br><span class="line"><span class="comment">#tf.Tensor(b&#x27;hello world&#x27;, shape=(), dtype=string)</span></span><br></pre></td></tr></table></figure>
<p>在tf.strings 模块中，提供了常见的字符串类型的工具函数，如小写化lower()、拼接<br>join()、长度length()、切分split()等。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">b = tf.strings.lower(a)</span><br><span class="line"><span class="built_in">print</span>(b)</span><br><span class="line"><span class="comment">#tf.Tensor(b&#x27;hello world&#x27;, shape=(), dtype=string)</span></span><br></pre></td></tr></table></figure>
<p>3、<strong>布尔类型</strong>：为了方便表达比较运算操作的结果，TensorFlow 还支持布尔类型(Boolean，简称bool)的张量。布尔类型的张量只需要传入Python 语言的布尔类型数据，转换成TensorFlow 内部布尔型。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">a = tf.constant(<span class="literal">True</span>) <span class="comment"># 创建布尔类型标量</span></span><br><span class="line"><span class="comment">#tf.Tensor(True, shape=(), dtype=bool)</span></span><br><span class="line">b = tf.constant([<span class="literal">True</span>, <span class="literal">False</span>]) <span class="comment"># 创建布尔类型向量</span></span><br><span class="line"><span class="comment">#tf.Tensor([ True False], shape=(2,), dtype=bool)</span></span><br></pre></td></tr></table></figure>
<h3 id="3-2-数值精度"><a href="#3-2-数值精度" class="headerlink" title="3.2 数值精度"></a>3.2 数值精度</h3><p>对于数值类型的张量，可以保存为不同字节长度的精度，如浮点数3.14 既可以保存为16 位(Bit)长度，也可以保存为32 位甚至64 位的精度。位越长，精度越高，同时占用的内存空间也就越大。常用的精度类型有tf.int16、tf.int32、tf.int64、tf.float16、tf.float32、tf.float64 等，其中tf.float64 即为tf.double.</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">a = tf.constant(<span class="number">123456789</span>, dtype=tf.int16)</span><br><span class="line">b = tf.constant(<span class="number">123456789</span>, dtype=tf.int32)</span><br><span class="line"><span class="comment">#tf.Tensor(-13035, shape=(), dtype=int16)</span></span><br><span class="line"><span class="comment">#tf.Tensor(123456789, shape=(), dtype=int32)</span></span><br></pre></td></tr></table></figure>
<p>可以看到，保存精度过低时，数据123456789 发生了溢出，得到了错误的结果，一般使用tf.int32、tf.int64 精度。对于浮点数，高精度的张量可以表示更精准的数据。</p>
<p><strong>读取精度</strong>：通过访问张量的dtype 成员属性可以判断张量的保存精度。对于某些只能处理指定精度类型的运算操作，需要提前检验输入张量的精度类型，并将不符合要求的张量进行类型转换。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;before:&#x27;</span>,a.dtype) <span class="comment"># 读取原有张量的数值精度</span></span><br><span class="line"><span class="keyword">if</span> a.dtype != tf.float32: <span class="comment"># 如果精度不符合要求，则进行转换</span></span><br><span class="line">  a = tf.cast(a,tf.float32) <span class="comment"># tf.cast 函数可以完成精度转换</span></span><br><span class="line"><span class="built_in">print</span>(<span class="string">&#x27;after :&#x27;</span>,a.dtype) <span class="comment"># 打印转换后的精度</span></span><br></pre></td></tr></table></figure>
<h3 id="3-3-待优化张量"><a href="#3-3-待优化张量" class="headerlink" title="3.3 待优化张量"></a>3.3 待优化张量</h3><p>为了区分需要计算梯度信息的张量与不需要计算梯度信息的张量，TensorFlow 增加了一种专门的数据类型来支持梯度信息的记录：tf.Variable。tf.Variable 类型在普通的张量类型基础上添加了name，trainable 等属性来支持计算图的构建。。由于梯度运算会消耗大量的计算资源，而且会自动更新相关参数，对于不需要的优化的张量，如神经网络的输入𝑿，不需要通过tf.Variable 封装。</p>
<p>通过 tf.Variable()函数可以将普通张量转换为待优化张量：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">a = tf.constant([-<span class="number">1</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>]) <span class="comment"># 创建TF 张量</span></span><br><span class="line">aa = tf.Variable(a) <span class="comment"># 转换为Variable 类型</span></span><br><span class="line"><span class="built_in">print</span>(aa.name)</span><br><span class="line"><span class="built_in">print</span>(aa.trainable)  <span class="comment"># Variable 类型张量的属性</span></span><br></pre></td></tr></table></figure>
<h3 id="3-4-创建张量"><a href="#3-4-创建张量" class="headerlink" title="3.4 创建张量"></a>3.4 创建张量</h3><p>TensorFlow 中，可以通过多种方式创建张量，如从Python 列表对象创建，从umpy 数组创建，或者创建采样自某种已知分布的张量等。</p>
<p>==<strong>1、从数组、列表对象创建</strong>:==</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">a = tf.convert_to_tensor([<span class="number">1</span>,<span class="number">2.</span>]) <span class="comment"># 从列表创建张量</span></span><br><span class="line"><span class="comment">#f.Tensor([1. 2.], shape=(2,), dtype=float32)</span></span><br><span class="line">b = tf.convert_to_tensor(np.array([[<span class="number">1</span>,<span class="number">2.</span>],[<span class="number">3</span>,<span class="number">4</span>]])) <span class="comment"># 从数组中创建张量</span></span><br><span class="line"><span class="comment">#tf.Tensor([[1. 2.] [3. 4.]], shape=(2, 2), dtype=float64)</span></span><br></pre></td></tr></table></figure>
<p>需要注意的是，Numpy 浮点数数组默认使用64 位精度保存数据，转换到Tensor 类型时精<br>度为tf.float64，可以在需要的时候将其转换为tf.float32 类型。</p>
<p>==<strong>2、创建全0 或全1 张量</strong>==</p>
<p>将张量创建为全0 或者全1 数据是非常常见的张量初始化手段。考虑线性变换𝒚 = 𝑾𝒙 + 𝒃，将权值矩阵𝑾初始化为全1 矩阵，偏置b 初始化为全0 向量，此时线性变化层输出𝒚 = 𝒙，因此是一种比较好的层初始化状态。通过tf.zeros()和tf.ones()即可创建任意形状，且内容全0 或全1 的张量。例如，创建为0 和为1 的标量：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">c = tf.zeros([])</span><br><span class="line">d = tf.ones([]) <span class="comment"># 创建全0，全1 的标量</span></span><br><span class="line"><span class="comment">#tf.Tensor(0.0, shape=(), dtype=float32)</span></span><br><span class="line"><span class="comment">#tf.Tensor(1.0, shape=(), dtype=float32)</span></span><br><span class="line">e = tf.zeros([<span class="number">2</span>,<span class="number">2</span>])<span class="comment">#创建全0 矩阵，指定shape 为2 行2 列</span></span><br><span class="line">f = tf.ones([<span class="number">3</span>,<span class="number">2</span>]) <span class="comment">#创建全1 矩阵，指定shape 为3 行2 列</span></span><br></pre></td></tr></table></figure>
<p>通过tf.zeros_like, tf.ones_like 可以方便地新建与某个张量shape 一致，且内容为全0 或全1 的张量。例如，创建与张量𝑨形状一样的全0 张量：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">tf.ones_like(a) <span class="comment"># 创建一个与a 形状相同，但是全1 的新矩阵</span></span><br><span class="line">tf.zeros_like(a) <span class="comment"># 创建一个与a 形状相同，但是全0 的新矩阵</span></span><br></pre></td></tr></table></figure>
<p>==<strong>3、创建自定义数值张量</strong>==</p>
<p>除了初始化为全0，或全1 的张量之外，有时也需要全部初始化为某个自定义数值的<br>张量，比如将张量的数值全部初始化为−1等。通过 tf.fill(shape, value)可以创建全为自定义数值value 的张量，形状由shape 参数指定。例如，创建元素为−1的标量：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">tf.fill([], -<span class="number">1</span>) <span class="comment"># 创建-1 的标量</span></span><br><span class="line">tf.fill([<span class="number">1</span>], -<span class="number">1</span>) <span class="comment"># 创建-1 的向量</span></span><br><span class="line">tf.fill([<span class="number">2</span>,<span class="number">2</span>], <span class="number">99</span>) <span class="comment"># 创建2 行2 列，元素全为99 的矩阵</span></span><br></pre></td></tr></table></figure>
<p>==<strong>3、创建已知分布的张量</strong>==</p>
<p>正态分布(Normal Distribution，或Gaussian Distribution)和均匀分布(Uniform Distribution)是最常见的分布之一，创建采样自这2 种分布的张量非常有用，比如在卷积神经网络中，卷积核张量𝑾初始化为正态分布有利于网络的训练；在对抗生成网络中，隐藏变量𝒛一般采样自均匀分布。</p>
<p>通过 tf.random.normal(shape, mean=0.0, stddev=1.0)可以创建形状为shape，均值为mean，标准差为stddev 的正态分布𝒩(mean, stddev2)。例如，创建均值为0，标准差为1的正态分布：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tf.random.normal([<span class="number">2</span>,<span class="number">2</span>]) <span class="comment"># 创建标准正态分布的张量</span></span><br></pre></td></tr></table></figure>
<p>通过tf.random.uniform(shape, minval=0, maxval=None, dtype=tf.float32)可以创建采样自[minval, maxval)区间的均匀分布的张量。例如创建采样自区间[0,1)，shape 为[2,2]的矩阵：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tf.random.uniform([<span class="number">2</span>,<span class="number">2</span>]) <span class="comment"># 创建采样自[0,1)均匀分布的矩阵</span></span><br></pre></td></tr></table></figure>
<p><strong>==5、创建序列==</strong></p>
<p>在循环计算或者对张量进行索引时，经常需要创建一段连续的整型序列，可以通过tf.range()函数实现。tf.range(limit, delta=1)可以创建[0, limit)之间，步长为delta 的整型序列，不包含limit 本身。例如，创建0~10，步长为1 的整型序列：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">tf.<span class="built_in">range</span>(<span class="number">10</span>) <span class="comment"># 0~10，不包含10</span></span><br><span class="line">tf.<span class="built_in">range</span>(<span class="number">10</span>,delta=<span class="number">2</span>)<span class="comment">#创建0~10，步长为2 的整形序列</span></span><br></pre></td></tr></table></figure>
<h3 id="3-5-张量的典型应用"><a href="#3-5-张量的典型应用" class="headerlink" title="3.5 张量的典型应用"></a>3.5 张量的典型应用</h3><p>==1、<strong>标量</strong>==：在 TensorFlow 中，标量最容易理解，它就是一个简单的数字，维度数为0，shape 为<br>[]。标量的一些典型用途是误差值的表示、各种测量指标的表示，比如准确度(Accuracy，简称acc)，精度(Precision)和召回率(Recall)等。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">out = tf.random.uniform([<span class="number">4</span>,<span class="number">10</span>]) <span class="comment">#随机模拟网络输出</span></span><br><span class="line">y = tf.constant([<span class="number">2</span>,<span class="number">3</span>,<span class="number">2</span>,<span class="number">0</span>]) <span class="comment"># 随机构造样本真实标签</span></span><br><span class="line">y = tf.one_hot(y, depth=<span class="number">10</span>) <span class="comment"># one-hot 编码</span></span><br><span class="line">loss = tf.keras.losses.mse(y, out) <span class="comment"># 计算每个样本的MSE</span></span><br><span class="line">loss = tf.reduce_mean(loss) <span class="comment"># 平均MSE,loss 应是标量</span></span><br><span class="line"><span class="built_in">print</span>(loss)</span><br><span class="line"><span class="comment">#tf.Tensor(0.19950335, shape=(), dtype=float32)</span></span><br></pre></td></tr></table></figure>
<p><strong>==2、向量==</strong>：向量是一种非常常见的数据载体，如在全连接层和卷积神经网络层中，偏置张量𝒃就<br>使用向量来表示。如图 4.2 所示，每个全连接层的输出节点都添加了一个偏置值，把所有输出节点的偏置表示成向量形式：$b=\left[ \begin{matrix} b_1 \ b_2 \end{matrix} \right] $</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210415091950106.png" alt=""></p>
<p>考虑 2 个输出节点的网络层，我们创建长度为2 的偏置向量𝒃，并累加在每个输出节点上：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">z = tf.random.normal([<span class="number">4</span>,<span class="number">2</span>])</span><br><span class="line">b = tf.zeros([<span class="number">2</span>]) <span class="comment"># 创建偏置向量</span></span><br><span class="line">z = z + b <span class="comment"># 累加上偏置向量</span></span><br></pre></td></tr></table></figure>
<p><strong>==3、矩阵==</strong>：矩阵也是非常常见的张量类型，比如全连接层的批量输入张量𝑿的形状为[𝑏, 𝑑in]，其<br>中𝑏表示输入样本的个数，即Batch Size，𝑑in表示输入特征的长度。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">x = tf.random.normal([<span class="number">2</span>,<span class="number">4</span>]) <span class="comment"># 2 个样本，特征长度为4 的张量</span></span><br><span class="line">w = tf.ones([<span class="number">4</span>,<span class="number">3</span>]) <span class="comment"># 定义W 张量</span></span><br><span class="line">b = tf.zeros([<span class="number">3</span>]) <span class="comment"># 定义b 张量</span></span><br><span class="line">o = x@w+b</span><br></pre></td></tr></table></figure>
<p>其中𝑿和𝑾张量均是矩阵，上述代码实现了一个线性变换的网络层，激活函数为空。一般地，𝜎(𝑿@𝑾 + 𝒃)网络层称为全连接层，在TensorFlow 中可以通过Dense 类直接实现，特别地，当激活函数𝜎为空时，全连接层也称为线性层。</p>
<p><strong>==4、三维张量==</strong>：三维的张量一个典型应用是表示序列信号，它的格式是：𝑿 = [𝑏, sequence len, feature len]。其中𝑏表示序列信号的数量，sequence len 表示序列信号在时间维度上的采样点数或步数，feature len 表示每个点的特征长度。后期NLP自然语言处理会用到。</p>
<p><strong>==5、四维张量==</strong>：四维张量在卷积神经网络中应用非常广泛，它用于保存特征图(Feature maps)数据，格式一般定义为：[𝑏, ℎ, w, 𝑐]。其中𝑏表示输入样本的数量，ℎ/ 分别表示特征图的高/宽，𝑐表示特征图的通道数。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210415094312131.png" alt=""></p>
<h3 id="3-6-索引与切片"><a href="#3-6-索引与切片" class="headerlink" title="3.6  索引与切片"></a>3.6  索引与切片</h3><p>通过索引与切片操作可以提取张量的部分数据。</p>
<p><strong>==1、索引==</strong>：在 TensorFlow 中，支持基本的[𝑖] [𝑗] ⋯标准索引方式，也支持通过逗号分隔索引号的索引方式。类似于C语言中的数组。</p>
<p><strong>==2、切片==</strong>：通过start: end: step切片方式可以方便地提取一段数据，其中start 为开始读取位置的索<br>引，end 为结束读取位置的索引(不包含end 位)，step 为采样步长。</p>
<h2 id="第四章-神经网络"><a href="#第四章-神经网络" class="headerlink" title="第四章 神经网络"></a>第四章 神经网络</h2><p>在第一章中已经介绍了神经元模型，当选择阶跃函数为激活函数时，虽然单个神经元结合阶跃函数能够完成简单的二分类问题，但是阶跃函数在𝑧 = 0处是不连续的，其他位置导数为0，无法利用梯度下降算法进行参数优化。</p>
<p>为了能够让感知机模型能够从数据中间自动学习，Frank Rosenblatt 提出了感知机（神经元）的学习算法：</p>
<div class="table-container">
<table>
<thead>
<tr>
<th style="text-align:left">More Actions算法 1：感知机训练算法</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">初始化参数𝒘 = 0, 𝑏 = 0</td>
</tr>
<tr>
<td style="text-align:left">repeat</td>
</tr>
<tr>
<td style="text-align:left">从训练集随机采样一个样本(𝒙𝑖, 𝑦𝑖)</td>
</tr>
<tr>
<td style="text-align:left">计算感知机的输出 = sign(𝒘𝐓𝒙𝑖 + 𝑏)</td>
</tr>
<tr>
<td style="text-align:left">如果 𝒙𝑖≠ 𝑦𝑖：</td>
</tr>
<tr>
<td style="text-align:left">输出：分类网络参数𝒘和𝑏</td>
</tr>
<tr>
<td style="text-align:left">𝒘′ ← 𝒘 + 𝜂 ∙ 𝑦𝑖 ∙ 𝒙𝒊</td>
</tr>
<tr>
<td style="text-align:left">𝑏′ ← 𝑏 + 𝜂 ∙ 𝑦𝑖</td>
</tr>
<tr>
<td style="text-align:left">until 训练次数达到要求</td>
</tr>
<tr>
<td style="text-align:left">输出：分类网络参数𝒘和𝑏</td>
</tr>
</tbody>
</table>
</div>
<p>其中𝜂为学习率。</p>
<p>但是以感知机为代表的线性模型不能解决异或(XOR)等线性不可分问题，尽管感知机模型不能解决线性不可分问题，但也可以通过嵌套多层神经网络可以解决。</p>
<h3 id="4-1-全连接层"><a href="#4-1-全连接层" class="headerlink" title="4.1 全连接层"></a><strong>4.1 全连接层</strong></h3><p>感知机模型的不可导特性严重约束了它的潜力，使得它只能解决极其简单的任务。现代深度学习动辄数百万甚至上亿的参数规模，但它的核心结构与感知机并没有多大差别。它在感知机的基础上，将不连续的阶跃激活函数换成了其它平滑连续可导的激活函数，并通过堆叠多个网络层来增强网络的表达能力。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210512125705818.png" alt=""></p>
<p>如上图所示，并行堆叠了2 个神经元，即2 个替换了激活函数的感知机，构成3 输入节点、2 个输出节点的网络层。其中第一个输出节点的输出为：</p>
<script type="math/tex; mode=display">
o_1 = 𝜎(𝑤_{11} ∙ 𝑥_1 + 𝑤_{21} ∙ 𝑥_2 + 𝑤_{31} ∙ 𝑥_3 + 𝑏_1)</script><p>第二个输出节点的输出为：</p>
<script type="math/tex; mode=display">
o_2 = 𝜎(𝑤_{12} ∙ 𝑥_1 + 𝑤_{22} ∙ 𝑥_2 + 𝑤_{32} ∙ 𝑥_3 + 𝑏_2)</script><p>输出向量为𝒐 = [𝑜1, 𝑜2]。整个网络层可以通过矩阵关系式表达：</p>
<script type="math/tex; mode=display">
\left[ \begin{matrix} o_1 & o_2 \end{matrix} \right]= \left[ \begin{matrix} x_1 & x_2 & x_3 \end{matrix} \right] \bullet  \left[ \begin{matrix} w_{11} & w_{12}\\ w_{21} & w_{22}\\ w_{31} & w_{32} \end{matrix} \right] + \left[ \begin{matrix} b_1 & b_2 \end{matrix} \right]</script><p>即: $ O =  X \bullet W + b $</p>
<p>其中输出矩阵𝑶包含了𝑏个样本的输出特征，由于每个输出节点与全部的输入节点相连接，这种网络层称为全连接层(Fully-connected Layer)，𝑾矩阵叫做全连接层的权值矩阵，𝒃向量叫做全连接层的偏置向量。</p>
<p>全连接层有两种实现方式：<strong>张量方式实现</strong>和<strong>层方式实现</strong></p>
<p><strong>1、张量方式实现</strong>：要实现全连接层，只需要定义好权值张量𝑾和偏置张量𝒃，并利用TensorFlow 提供的批量矩阵相乘函数tf.matmul()即可完成网络层的计算。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">##创建输入𝑿矩阵为𝑏 = 2个样本，每个样本的输入特征长度为𝑑in = 784，输出节点数为𝑑out = 256，故定义权值矩阵𝑾的shape 为[784,256]，并采用正态分布初始化𝑾；</span></span><br><span class="line">xx = tf.random.normal([<span class="number">2</span>,<span class="number">784</span>])</span><br><span class="line"><span class="comment">##偏置向量𝒃的shape 定义为[256]，在计算完𝑿@𝑾后相加即可，</span></span><br><span class="line">ww1 = tf.Variable(tf.random.truncated_normal([<span class="number">784</span>, <span class="number">256</span>], stddev=<span class="number">0.1</span>))</span><br><span class="line"><span class="comment">##最终全连接层的输出𝑶的shape 为[2,256]，即2 个样本的特征，每个特征长度为256</span></span><br><span class="line">bb1 = tf.Variable(tf.zeros([<span class="number">256</span>]))</span><br><span class="line">oo1 = tf.matmul(x,w1) + b1 <span class="comment"># 线性变换</span></span><br><span class="line">oo1 = tf.nn.relu(o1) <span class="comment"># 激活函数</span></span><br></pre></td></tr></table></figure>
<p><strong>2、层方式实现：</strong></p>
<blockquote>
<p>全连接层本质上是矩阵的相乘和相加运算，实现并不复杂。但是作为最常用的网络层之一，TensorFlow 中有更高层、使用更方便的层实现方式：layers.Dense(units, activation)。通过layer.Dense 类，只需要指定输出节点数Units 和激活函数类型activation 即可。需要注意的是，输入节点数会根据第一次运算时的输入shape 确定，同时根据输入、输出节点数自动创建并初始化权值张量𝑾和偏置张量𝒃，因此在新建类Dense 实例时，并不会立即创建权值张量𝑾和偏置张量𝒃，而是需要调用build 函数或者直接进行一次前向计算，才能完成网络参数的创建。其中activation 参数指定当前层的激活函数，可以为常见的激活函数或自定义激活函数，也可以指定为None，即无激活函数。</p>
</blockquote>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">x = tf.random.normal([<span class="number">4</span>,<span class="number">28</span>*<span class="number">28</span>])</span><br><span class="line"><span class="keyword">from</span> tensorflow.keras <span class="keyword">import</span> layers <span class="comment"># 导入层模块</span></span><br><span class="line"><span class="comment"># 创建全连接层，指定输出节点数和激活函数</span></span><br><span class="line">fc = layers.Dense(<span class="number">512</span>, activation=tf.nn.relu)</span><br><span class="line">h1 = fc(x) <span class="comment"># 通过fc 类实例完成一次全连接层的计算，返回输出张量</span></span><br></pre></td></tr></table></figure>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210512124340498.png" alt=""></p>
<p>上述通过一行代码即可以创建一层全连接层fc，并指定输出节点数为512，输入的节点数在fc(x)计算时自动获取，并创建内部权值张量𝑾和偏置张量𝒃。</p>
<h3 id="4-2-神经网络"><a href="#4-2-神经网络" class="headerlink" title="4.2 神经网络"></a>4.2 神经网络</h3><p>通过层层堆叠的全连接层，保证前一层的输出节点数与当前层的输入节点数匹配，即可堆叠出任意层数的网络。我们把这种由神经元相互连接而成的网络叫做神经网络。通过堆叠4 个全连接层，可以获得层数为4 的神经网络，由于每层均为全连接层，称为全连接网络，其中第1~3 个全连接层在网络中间，称之为隐藏层1、2、3，最后一个全连接层的输出作为网络的输出，称为输出层。隐藏层1、2、3 的输出节点数分别为[256,128,64]，输出层的输出节点数为10。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210512125014472.png" alt=""></p>
<p>采用tf.keras实现上述网络：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> tensorflow <span class="keyword">as</span> tf</span><br><span class="line">x = tf.random.normal([<span class="number">4</span>,<span class="number">28</span>*<span class="number">28</span>])</span><br><span class="line"><span class="comment"># 导入常用网络层layers</span></span><br><span class="line"><span class="keyword">from</span> tensorflow.keras <span class="keyword">import</span> layers,Sequential</span><br><span class="line">fc1 = layers.Dense(<span class="number">256</span>, activation=tf.nn.relu) <span class="comment"># 隐藏层1</span></span><br><span class="line">fc2 = layers.Dense(<span class="number">128</span>, activation=tf.nn.relu) <span class="comment"># 隐藏层2</span></span><br><span class="line">fc3 = layers.Dense(<span class="number">64</span>, activation=tf.nn.relu) <span class="comment"># 隐藏层3</span></span><br><span class="line">fc4 = layers.Dense(<span class="number">10</span>, activation=<span class="literal">None</span>) <span class="comment"># 输出层</span></span><br><span class="line"></span><br><span class="line">x = tf.random.normal([<span class="number">4</span>,<span class="number">28</span>*<span class="number">28</span>])</span><br><span class="line">h1 = fc1(x) <span class="comment"># 通过隐藏层1 得到输出</span></span><br><span class="line">h2 = fc2(h1) <span class="comment"># 通过隐藏层2 得到输出</span></span><br><span class="line">h3 = fc3(h2) <span class="comment"># 通过隐藏层3 得到输出</span></span><br><span class="line">h4 = fc4(h3) <span class="comment"># 通过输出层得到网络输出</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 导入Sequential 容器</span></span><br><span class="line"><span class="keyword">from</span> tensorflow.keras <span class="keyword">import</span> layers,Sequential</span><br><span class="line"><span class="comment"># 通过Sequential 容器封装为一个网络类</span></span><br><span class="line">model = Sequential([</span><br><span class="line">layers.Dense(<span class="number">256</span>, activation=tf.nn.relu) , <span class="comment"># 创建隐藏层1</span></span><br><span class="line">layers.Dense(<span class="number">128</span>, activation=tf.nn.relu) , <span class="comment"># 创建隐藏层2</span></span><br><span class="line">layers.Dense(<span class="number">64</span>, activation=tf.nn.relu) , <span class="comment"># 创建隐藏层3</span></span><br><span class="line">layers.Dense(<span class="number">10</span>, activation=<span class="literal">None</span>) , <span class="comment"># 创建输出层</span></span><br><span class="line">])</span><br></pre></td></tr></table></figure>
<h3 id="4-3-优化目标"><a href="#4-3-优化目标" class="headerlink" title="4.3 优化目标"></a>4.3 优化目标</h3><p>神经网络从输入到输出的计算过程叫做前向传播(Forward Propagation)或前向计算。神经网络的前向传播过程，也是数据张量(Tensor)从第一层流动(Flow)至输出层的过程，即从输入数据开始，途径每个隐藏层，直至得到输出并计算误差，这也是TensorFlow框架名字由来。</p>
<p>前向传播的最后一步就是完成误差的计算：</p>
<script type="math/tex; mode=display">
ℒ = 𝑔(𝑓_𝜃(𝒙), 𝒚)</script><p>其中𝑓<sub>𝜃</sub>(∙)代表了利用𝜃参数化的神经网络模型，𝑔(∙)称之为误差函数，用来描述当前网络的预测值𝑓<sub>𝜃</sub>(𝒙)与真实标签𝒚之间的差距度量，比如常用的均方差误差函数。ℒ称为网络的误差(Error，或损失Loss)，一般为标量。我们希望通过在训练集𝔻<sup>train</sup>上面学习到一组参数𝜃使得训练的误差ℒ最小：</p>
<script type="math/tex; mode=display">
\theta^{*}=\underbrace{\arg \min }_{\theta} g\left(f_{\theta}(x), y\right), x \in \mathbb{D}^{\text {train }}</script><p>上述的最小化优化问题一般采用误差反向传播(Backward Propagation，简称BP)算法来求解网络参数𝜃的梯度信息，并利用梯度下降(Gradient Descent，简称GD)算法迭代更新参数：</p>
<script type="math/tex; mode=display">
𝜃′ = 𝜃 − 𝜂 ∙ ∇𝜃ℒ</script><p>𝜂为学习率;</p>
<h3 id="4-4-激活函数"><a href="#4-4-激活函数" class="headerlink" title="4.4 激活函数"></a>4.4 激活函数</h3><p>下面介绍神经网络中的常见激活函数，与阶跃函数和符号函数不同，这些函数都是平滑可导的，适合于梯度下降算法。</p>
<p><strong>1、Sigmoid</strong>：Sigmoid 函数也叫Logistic 函数，定义为</p>
<script type="math/tex; mode=display">
sigmoid(x) = \frac{1}{1+e^{-x}}</script><p>它的一个优良特性就是能够把𝑥 ∈ 𝑅的输入“压缩”到𝑥 ∈ (0,1)区间，这个区间的数值在机器学习常用来表示以下意义：</p>
<ul>
<li>概率分布 (0,1)区间的输出和概率的分布范围[0,1]契合，可以通过Sigmoid 函数将输出转译为概率输出</li>
<li>信号强度 一般可以将0~1 理解为某种信号的强度，如像素的颜色强度，1 代表当前通道颜色最强，0 代表当前通道无颜色；抑或代表门控值(Gate)的强度，1 代表当前门控全部开放，0 代表门控关闭Sigmoid 函数连续可导</li>
</ul>
<p>Sigmoid 函数连续可导，可以直接利用梯度下降算法优化网络参数，应用的非常广泛。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210512210001845.png" alt=""></p>
<p>在 TensorFlow 中，可以通过tf.nn.sigmoid 实现Sigmoid 函数，代码如下：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tf.nn.sigmoid(x) <span class="comment"># 通过Sigmoid 函数</span></span><br></pre></td></tr></table></figure>
<p><strong>2、 ReLU：</strong>ReLU 函数定义为：</p>
<script type="math/tex; mode=display">
ReLU(𝑥) ≜ max(0, 𝑥)</script><p>可以看到，ReLU 对小于0 的值全部抑制为0；对于正数则直接输出，这种单边抑制特性来源于生物学。它具有单侧抑制、相对宽松的兴奋边界等特性</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210513182832791.png" alt=""></p>
<p>在 TensorFlow 中，可以通过tf.nn.relu 实现ReLU 函数，代码如下：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tf.nn.relu(x) <span class="comment"># 通过ReLU 激活函数</span></span><br></pre></td></tr></table></figure>
<h3 id="4-5-输出层设计"><a href="#4-5-输出层设计" class="headerlink" title="4.5 输出层设计"></a>4.5 输出层设计</h3><p>神经网络的最后一层除了和所有的隐藏层一样，完成维度变换、特征提取的功能，还作为输出层使用，需要根据具体的任务场景来决定是否使用激活函数，以及使用什么类型的激活函数等。需要根据输出值的区间范围来分类讨论。常见的几种输出类型包括：</p>
<ul>
<li>𝑜𝑖 ∈ 𝑅𝑑 输出属于整个实数空间，或者某段普通的实数空间，比如函数值趋势的预测，年龄的预测问题等。</li>
<li>𝑜𝑖 ∈ [0,1] 输出值特别地落在[0, 1]的区间，如图片生成，图片像素值一般用[0, 1]区间的值表示；或者二分类问题的概率，如硬币正反面的概率预测问题。</li>
<li>𝑜𝑖 ∈ [0, 1], 𝑖 𝑜𝑖 = 1 输出值落在[0,1]的区间，并且所有输出值之和为1，常见的如多分类问题，如MNIST 手写数字图片识别，图片属于10 个类别的概率之和应为1。</li>
<li>𝑜𝑖 ∈ [−1, 1] 输出值在[-1, 1]之间</li>
</ul>
<p><strong>1、普通实数空间</strong></p>
<p>这一类问题比较普遍，像正弦函数曲线预测、年龄的预测、股票走势的预测等都属于整个或者部分连续的实数空间，输出层可以不加激活函数。误差的计算直接基于最后一层的输出𝒐和真实值𝒚进行计算，如采用均方差误差函数度量输出值𝒐与真实值𝒚之间的距离：</p>
<script type="math/tex; mode=display">
ℒ = 𝑔(𝒐, 𝒚)</script><p>其中𝑔代表了某个具体的误差计算函数，例如MSE 等。</p>
<p><strong>2 、[0, 1]区间</strong></p>
<p>输出值属于[0, 1]区间也比较常见，比如图片的生成、二分类问题等。在机器学习中，一般会将图片的像素值归一化到[0,1]区间，如果直接使用输出层的值，像素的值范围会分布在整个实数空间。为了让像素的值范围映射到[0,1]的有效实数空间，需要在输出层后添加某个合适的激活函数𝜎，其中Sigmoid 函数刚好具有此功能。</p>
<p><strong>3、 [0,1]区间，和为1</strong></p>
<p>输出值𝑜𝑖 ∈ [0,1]，且所有输出值之和为1，这种设定以多分类问题最为常见，输出层的每个输出节点代表了一种类别。考虑多分类问题中的样本只可能属于所有类别中的某一种，因此满足所有类别概率之和为1 的约束。</p>
<p>可以通过在输出层添加Softmax 函数实现。Softmax 函数定义为：</p>
<script type="math/tex; mode=display">
Softmax(x) = \frac{e^{z_i}}{\sum_{j = 1}^{d_{out}}e^{z_j}}</script><p>Softmax 函数不仅可以将输出值映射到[0,1]区间，还满足所有的输出值之和为1 的特性。</p>
<p>例如输出层的输出为[2.0,1.0,0.1]，经过Softmax 函数计算后，得到输出为[0.7,0.2,0.1]，每个值代表了当前样本属于每个类别的概率，概率值之和为1。通过Softmax函数可以将输出层的输出转译为类别概率，在分类问题中使用的非常频繁。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210513192502831.png" alt=""></p>
<p><strong>4、 [-1, 1]</strong></p>
<p>果希望输出值的范围分布在(−1, 1)区间，可以简单地使用tanh 激活函数，实现如下：</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210513193007115.png" alt=""></p>
<h3 id="4-6-误差计算"><a href="#4-6-误差计算" class="headerlink" title="4.6 误差计算"></a>4.6 误差计算</h3><p>在搭建完模型结构后，下一步就是选择合适的误差函数来计算误差。常见的误差函数有均方差、交叉熵、KL 散度、Hinge Loss 函数等，其中均方差函数和交叉熵函数在深度学习中比较常见，均方差函数主要用于回归问题，交叉熵函数主要用于分类问题。</p>
<p><strong>1、均方差误差函数：</strong></p>
<p>均方差(Mean Squared Error，简称MSE)误差函数把输出向量和真实向量映射到笛卡尔坐标系的两个点上，通过计算这两个点之间的欧式距离(准确地说是欧式距离的平方)来衡量两个向量之间的差距：</p>
<script type="math/tex; mode=display">
\operatorname{MSE}(\boldsymbol{y}, \boldsymbol{o}) \triangleq \frac{1}{d_{\text {out }}} \sum_{i=1}^{d_{\text {out }}}\left(y_{i}-o_{i}\right)^{2}</script><p>MSE 误差函数的值总是大于等于0，当MSE 函数达到最小值0 时，输出等于真实标签，此时神经网络的参数达到最优状态。</p>
<p>均方差误差函数广泛应用在回归问题中，实际上，分类问题中也可以应用均方差误差函数。在TensorFlow 中，可以通过函数方式或层方式实现MSE 误差计算。例如，使用函数方式实现MSE 计算，代码如下：</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210513194833853.png" alt=""></p>
<h2 id="第五章-反向传播算法"><a href="#第五章-反向传播算法" class="headerlink" title="第五章 反向传播算法"></a>第五章 反向传播算法</h2><p>误差反向传播算法（Backpropagation，简称BP），反向传播算法和梯度下降算法是神经网络的核心算法，深刻理解其工作原理十分重要。先回顾导数、梯度等数学概念，然后推导常用激活函数、损失函数的梯度形式，并开始逐渐推导感知机、多层神经网络的梯度传播方式。</p>
<h3 id="5-1-导数与梯度"><a href="#5-1-导数与梯度" class="headerlink" title="5.1 导数与梯度"></a>5.1 导数与梯度</h3><p>导数（Derivative）的定义是自变量𝑥产生一个微小扰动Δ𝑥后，函数输出值的增量Δ𝑦与自变量增量Δ𝑥的比值在Δ𝑥趋于0 时的极限𝑎，如果存在，𝑎即为在𝑥处的导数：</p>
<script type="math/tex; mode=display">
a = \lim_{\Delta x \rightarrow 0} {\frac{f(x+\Delta x)-f(x)}{\Delta x}}</script><p>函数的导数可以记为𝑓′(𝑥)或$\frac {d𝑦}{d𝑥}$，从几何角度来看，一元函数在某处的导数就是函数的切线在此处的斜率，即函数值沿着𝑥方向的变化率。</p>
<p>针对多元函数而言，当函数的自变量数大于一个时，函数的导数概念拓展为函数值沿着任意Δ𝒙方向的变化率。导数本身是标量，没有方向，但是导数表征了函数值在某个方向Δ𝒙的上变化率。在这些任意Δ𝒙方向中，沿着坐标轴的几个方向比较特殊，此时的导数也叫作偏导数（Partial Derivative）。对于多元函数的偏导数，记为$\frac{\partial y}{\partial x<em>{1}}, \frac{\partial y}{\partial x</em>{2}}, \cdots$</p>
<p>例如本质上为多元函数的神经网络模型，shape 为[784, 256]的权值矩阵𝑾，它包含了784 × 256个连接权值𝑤，需要求出784 × 256个偏导数。需要注意的是，在数学表达习惯中，一般要讨论的自变量记为𝒙，但是在神经网络中，𝒙一般用来表示输入，比如图片、文本、语音数据等，网络的自变量是网络参数集𝜃 = {𝑤1, 𝑏1, 𝑤2, 𝑏2, ⋯ }。利用梯度下降算法优化网络时，需要求出网络的所有偏导数。因此，我们关心的也是误差函数输出ℒ沿着自变量𝜃𝑖方向上的导数，即ℒ对网络参数𝜃𝑖的偏导数$\frac{\partial l}{\partial w_1}, \frac{\partial l}{\partial b_2}, \cdots$  等。把函数所有偏导数写成向量形式：</p>
<script type="math/tex; mode=display">
\nabla_{\theta} \mathcal{L}=\left(\frac{\partial \mathcal{L}}{\partial \theta_{1}}, \frac{\partial \mathcal{L}}{\partial \theta_{2}}, \frac{\partial \mathcal{L}}{\partial \theta_{3}}, \cdots, \frac{\partial \mathcal{L}}{\partial \theta_{n}}\right)</script><p>此时梯度下降算法可以按着向量形式进行更新：</p>
<script type="math/tex; mode=display">
\theta^{\prime}=\theta-\eta \cdot \nabla_{\theta} \mathcal{L}</script><p>𝜂为学习率超参数。梯度下降算法一般是寻找函数ℒ的最小值，有时也希望求解函数的最大值，如强化学习中希望最大化回报函数，则可按着梯度方向更新：</p>
<script type="math/tex; mode=display">
\theta^{\prime}=\theta+\eta \cdot \nabla_{\theta} \mathcal{L}</script><p>这种更新方式称为梯度上升算法。梯度下降算法和梯度上升算法思想上是相同的，一是朝<br>着梯度的反向更新，一是朝着梯度的方向更新，两者都需要求解偏导数。这里把向量<br>($\frac{\partial l}{\partial \theta_1}, \frac{\partial l}{\partial \theta_2}, \cdots \frac{\partial l}{\partial \theta_n}$  )称为函数的梯度（Gradient）。</p>
<p>但是通过梯度下降算法并不能保证得到全局最优解，这主要是目标函数的非凸性造成的。如图所示，深蓝色区域为极小值区域，不同的优化轨迹可能得到不同的最优数值解，这些数值解并不一定是全局最优解。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210526130047428.png" alt=""></p>
<blockquote>
<p>神经网络的模型表达式通常非常复杂，模型参数量可达千万、数亿级别，几乎所有的神经网络的优化问题都是依赖于深度学习框架去自动计算网络参数的梯度，然后采用梯度下降算法循环迭代优化网络的参数直至性能满足需求。深度学习框架这里主要实现的算法就是反向传播算法和梯度下降算法，因此理解这两个算法的原理有利于了解深度学习框架的作用。</p>
</blockquote>
<h3 id="5-2-激活函数导数"><a href="#5-2-激活函数导数" class="headerlink" title="5.2 激活函数导数"></a>5.2 激活函数导数</h3><p>Sigmoid函数表达式：<script type="math/tex">\sigma(x) = \frac{1}{1 + e^{-x}}</script></p>
<p>Sigmoid函数的导数表达式：</p>
<script type="math/tex; mode=display">
\begin{array}{c}
\frac{\mathrm{d}}{\mathrm{d} x} \sigma(x)=\frac{\mathrm{d}}{\mathrm{d} x}\left(\frac{1}{1+e^{-x}}\right) \\
=\frac{\mathrm{e}^{-x}}{\left(1+\mathrm{e}^{-x}\right)^{2}} \\
=\frac{\left(1+\mathrm{e}^{-x}\right)-1}{\left(1+\mathrm{e}^{-x}\right)^{2}} \\
=\frac{1+e^{-x}}{\left(1+e^{-x}\right)^{2}}-\left(\frac{1}{1+e^{-x}}\right)^{2} \\
=\sigma(x)-\sigma(x)^{2} \\
=\sigma(1-\sigma)
\end{array}</script><p>可以看到，Sigmoid 函数的导数表达式最终可以表达为激活函数的输出值的简单运算，利用这一性质，在神经网络的梯度计算中，通过缓存每层的Sigmoid 函数输出值，即可在需要的时候计算出其导数。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">sigmoid</span>(<span class="params">x</span>): </span><br><span class="line">    <span class="comment"># 实现 sigmoid 函数</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span> / (<span class="number">1</span> + np.exp(-x))</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">sigmoid_derivative</span>(<span class="params">x</span>): </span><br><span class="line">    <span class="comment"># sigmoid 导数的计算</span></span><br><span class="line">    <span class="comment"># sigmoid 函数的表达式由手动推导而得</span></span><br><span class="line">    <span class="keyword">return</span> sigmoid(x)*(<span class="number">1</span>-sigmoid(x))</span><br></pre></td></tr></table></figure>
<p><img src="C:\Users\zhou\AppData\Roaming\Typora\typora-user-images\image-20210527211709757.png" alt=""></p>
<p> ReLU 函数的导数表达式：$ReLU(𝑥) = max(0, 𝑥)$</p>
<p>它的导数推导非常简单，直接可得：</p>
<script type="math/tex; mode=display">
\frac{\mathrm{d}}{\mathrm{d} x} \operatorname{ReLU}=\left\{\begin{array}{ll}
1 & x \geq 0 \\
0 & x<0
\end{array}\right.</script><p>可以看到，ReLU 函数的导数计算简单，x 大于等于零的时候，导数值恒为1，在反向传播过程中，它既不会放大梯度，造成梯度爆炸(Gradient exploding)现象；也不会缩小梯度，造成梯度弥散(Gradient vanishing)现象。</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">relu</span>(<span class="params">x</span>):</span><br><span class="line">    <span class="keyword">return</span> np.maximum(<span class="number">0</span>, x)</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">relu_derivative</span>(<span class="params">x</span>): <span class="comment"># ReLU 函数的导数</span></span><br><span class="line">    d = np.array(x, copy=<span class="literal">True</span>) <span class="comment"># 用于保存梯度的张量</span></span><br><span class="line">    d[x &lt; <span class="number">0</span>] = <span class="number">0</span> <span class="comment"># 元素为负的导数为 0</span></span><br><span class="line">    d[x &gt;= <span class="number">0</span>] = <span class="number">1</span> <span class="comment"># 元素为正的导数为 1</span></span><br><span class="line">    <span class="keyword">return</span> d</span><br></pre></td></tr></table></figure>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210527211931148.png" alt=""></p>
<p>在 ReLU 函数被广泛应用之前，神经网络中激活函数采用Sigmoid 居多，但是Sigmoid函数容易出现梯度弥散现象，当网络的层数增加后，较前层的参数由于梯度值非常微小，参数长时间得不到有效更新，无法训练较深层的神经网络，导致神经网络的研究一直停留在浅层。</p>
<p>Tanh 函数梯度：tanh 函数的表达式：</p>
<script type="math/tex; mode=display">
\begin{array}{l}
\tanh (x)=\frac{\left(e^{x}-e^{-x}\right)}{\left(e^{x}+e^{-x}\right)} \\
=2 \cdot \operatorname{sigmoid}(2 x)-1
\end{array}</script><p>它的导数推导为:</p>
<script type="math/tex; mode=display">
\begin{aligned}
\frac{\mathrm{d}}{\mathrm{d} x} \tanh (x)=& \frac{\left(e^{x}+e^{-x}\right)\left(e^{x}+e^{-x}\right)-\left(e^{x}-e^{-x}\right)\left(e^{x}-e^{-x}\right)}{\left(e^{x}+e^{-x}\right)^{2}} \\
&=1-\frac{\left(e^{x}-e^{-x}\right)^{2}}{\left(e^{x}+e^{-x}\right)^{2}}=1-\tanh ^{2}(x)
\end{aligned}</script><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">tanh</span>(<span class="params">x</span>): <span class="comment"># tanh 函数实现</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">2</span>*sigmoid(<span class="number">2</span>*x) - <span class="number">1</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">tanh_derivative</span>(<span class="params">x</span>): <span class="comment"># tanh 导数实现</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>-tanh(x)**<span class="number">2</span></span><br></pre></td></tr></table></figure>
<p><img src="C:\Users\zhou\AppData\Roaming\Typora\typora-user-images\image-20210527212906988.png" alt=""></p>
<h3 id="5-3-损失函数梯度"><a href="#5-3-损失函数梯度" class="headerlink" title="5.3 损失函数梯度"></a>5.3 损失函数梯度</h3><p>均方误差损失函数表达式为：$\mathcal{L}=\frac{1}{2} \sum<em>{k=1}^{K}\left(y</em>{k}-o_{k}\right)^{2}$</p>
<p>上式中的$\frac {1}{2}$项用于简化计算，也可以利用$\frac{1}{k}$进行平均，这些缩放运算均不会改变梯度方向。则它的偏导数$\frac {∂ℒ}{∂𝑜_𝑖}$可以展开为：</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial o_{i}}=\frac{1}{2} \sum_{k=1}^{K} \frac{\partial}{\partial o_{i}}\left(y_{k}-o_{k}\right)^{2}</script><p>利用复合函枚导数法则分解为:</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial \boldsymbol{o}_{\boldsymbol{i}}}=\frac{1}{2} \sum_{k=1}^{K}{ }_{k=1} \cdot\left(y_{k}-o_{k}\right) \cdot \frac{\partial\left(y_{k}-o_{k}\right)}{\partial o_{i}}</script><p>即</p>
<script type="math/tex; mode=display">
\begin{aligned}
\frac{\partial \mathcal{L}}{\partial o_{i}} &=\sum_{k=1}^{K}\left(y_{k}-o_{k}\right) \cdot-1 \cdot \frac{\partial o_{k}}{\partial o_{i}} \\
&=\sum_{k=1}^{K}\left(o_{k}-y_{k}\right) \cdot \frac{\partial o_{k}}{\partial o_{l}}
\end{aligned}</script><p>考虑到$\frac {𝜕𝑜<em>𝑘}{𝜕𝑜</em>𝑖}$仅当𝑘 = 𝑖时才为1，其它点都为0，也就是说，偏导数$\frac {𝜕ℒ}{𝜕𝑜𝑖}$只与第𝑖号节点相关，与其它节点无关，因此上式中的求和符号可以去掉。均方误差函数的导数可以推导为：</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial o_{i}}=\left(o_{i}-y_{i}\right)</script><h3 id="5-4-全连接层梯度"><a href="#5-4-全连接层梯度" class="headerlink" title="5.4 全连接层梯度"></a>5.4 全连接层梯度</h3><p>单神经元梯度：</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210604094102790.png" alt=""></p>
<p>如果我们采用均方误差函数，考虑到单个神经元只有一个输出𝑜<sub>1</sub><sup>(1)</sup>，那么损失可以表达为：</p>
<script type="math/tex; mode=display">
L =\frac{1}{2}(o_1^{(1)}-t)^2 = (o_1-t)^2</script><p>其中𝑡为真实标签值，添加$\frac{1}{2}$并不影响梯度的方向，计算更简便。我们以权值连接的第𝑗 ∈[1,j]号节点的权值变量𝑤<sub>j1</sub>为例，考虑损失函数ℒ对其的偏导数$\frac{\partial \mathcal{L}}{\partial w_{j1}}$</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w_{j1}} = (o_1-t)\frac{\partial \mathcal{o_1}}{\partial w_{j1}}</script><p>全连接层梯度:把单个神经元模型推广到单层的全连接层的网络上，输入层通过一个全连接层得到输出向量𝒐(1)，与真实标签向量𝒕计算均方误差。输入节点数为J，输出节点数为𝐾。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210604101535767.png" alt=""></p>
<p>多输出的全连接网络层模型与单个神经元模型不同之处在于，它多了很多的输出节点𝑜<sub>1</sub><sup>(1)</sup>, 𝑜<sub>2</sub><sup>(1)</sup>,, 𝑜<sub>3</sub><sup>(1)</sup>,, ⋯ , 𝑜<sub>k</sub><sup>(1)</sup>,，每个输出节点分别对应到真实标签𝑡<sub>1</sub>, 𝑡<sub>2</sub>, … , 𝑡<sub>k</sub>。𝑤<sub>k</sub>是输入第𝑗好节点与输出第𝑘号节点的连接权值。均方误差可以表达为：</p>
<script type="math/tex; mode=display">
L=\frac{1}{2} \sum_{k=1}^{K} \left(o_{i}^{(1)}-t_{i}\right)^{2}</script><p>这里简单使用TensorFlow 自动求导功能，来体验链式法则求解全连接层梯度的魅力。例如：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> tensorflow <span class="keyword">as</span> tf</span><br><span class="line"><span class="comment"># 构建待优化变量</span></span><br><span class="line">x = tf.constant(<span class="number">1.</span>)</span><br><span class="line">w1 = tf.constant(<span class="number">2.</span>)</span><br><span class="line">b1 = tf.constant(<span class="number">1.</span>)</span><br><span class="line">w2 = tf.constant(<span class="number">2.</span>)</span><br><span class="line">b2 = tf.constant(<span class="number">1.</span>)</span><br><span class="line"><span class="comment"># 构建梯度记录器</span></span><br><span class="line"><span class="keyword">with</span> tf.GradientTape(persistent=<span class="literal">True</span>) <span class="keyword">as</span> tape:</span><br><span class="line">    <span class="comment"># 非tf.Variable 类型的张量需要人为设置记录梯度信息</span></span><br><span class="line">    tape.watch([w1, b1, w2, b2])</span><br><span class="line">    <span class="comment"># 构建2 层线性网络</span></span><br><span class="line">    y1 = x * w1 + b1</span><br><span class="line">    y2 = y1 * w2 + b2</span><br><span class="line"><span class="comment"># 独立求解出各个偏导数</span></span><br><span class="line">dy2_dy1 = tape.gradient(y2, [y1])[<span class="number">0</span>]</span><br><span class="line">dy1_dw1 = tape.gradient(y1, [w1])[<span class="number">0</span>]</span><br><span class="line">dy2_dw1 = tape.gradient(y2, [w1])[<span class="number">0</span>]</span><br><span class="line"><span class="comment"># 验证链式法则，2 个输出应相等</span></span><br><span class="line"><span class="built_in">print</span>(dy2_dy1 * dy1_dw1)</span><br><span class="line"><span class="built_in">print</span>(dy2_dw1)</span><br></pre></td></tr></table></figure>
<p>以上代码，通过自动求导功能计算出$\frac{\partial \mathcal{y_2}}{\partial y_1},\frac{\partial \mathcal{y_1}}{\partial w_1}和\frac{\partial \mathcal{y_2}}{\partial w_1}$，借助链式法则我们可以推断$\frac{\partial \mathcal{y_2}}{\partial y_1},\frac{\partial \mathcal{y_1}}{\partial w_1}和\frac{\partial \mathcal{y_2}}{\partial w_1}$应该是相等的，它们的计算结果如下：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">tf.Tensor(<span class="number">2.0</span>, shape=(), dtype=float32)</span><br><span class="line">tf.Tensor(<span class="number">2.0</span>, shape=(), dtype=float32)</span><br></pre></td></tr></table></figure>
<p>可以看到$\frac{\partial \mathcal{y_2}}{\partial y_1},\frac{\partial \mathcal{y_1}}{\partial w_1}和\frac{\partial \mathcal{y_2}}{\partial w_1}$，偏导数的传播是符合链式法则的。</p>
<h3 id="5-5-反向传播算法"><a href="#5-5-反向传播算法" class="headerlink" title="5.5 反向传播算法"></a>5.5 反向传播算法</h3><p>现在来推导隐藏层的梯度传播规律。简单回顾一下输出层的偏导数公式：</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w_{j k}}=\left(o_{k}-t_{k}\right) o_{k}\left(1-o_{k}\right) x_{j}=\delta_{k} x_{j}</script><p>考虑倒数第二层的偏导数 $\frac{\partial \mathcal{L}}{\partial w<em>{i j}}$, 输出层节点数为 K，输出为 $\boldsymbol{0}^{(K)}=\left[o</em>{1}^{(K)}, o<em>{2}^{(K)}, \cdots, o</em>{K}^{(K)}\right] $ ；倒数第二层节点数为 $\mathrm{J}$, 输出为 $\boldsymbol{o}^{(J)}=\left[o<em>{1}^{(J)}, o</em>{2}^{(I)}, \cdots, o<em>{J}^{(J)}\right] $； 倒数第三层的节点数为 $\mathrm{I}$, 输出为 $\boldsymbol{o}^{(I)}=\left[o</em>{1}^{(I)}, o<em>{2}^{(I)}, \cdots, o</em>{I}^{(I)}\right] $。</p>
<p><img src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/image-20210604103815539.png" alt=""></p>
<p>由于 $\mathcal{L}$ 通过每个输出节点o $<em>{k}$ 与 $w</em>{i j}$ 相关联，故此处不能去掉求和符号，运用链式法则将均方 差函数拆解：</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w_{i j}}=\sum_{k}\left(o_{k}-t_{k}\right) \frac{\partial}{\partial w_{i j}} o_{k}</script><p>将o $<em>{k}=\sigma\left(z</em>{k}\right)$ 代入可得:</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w_{i j}}=\sum_{k}\left(o_{k}-t_{k}\right) \frac{\partial}{\partial w_{i j}} \sigma\left(z_{k}\right)</script><p>利用 Sigmoid 函数的导数 $\sigma^{\prime}=\sigma(1-\sigma)$ 进一步分解为:</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w_{i j}}=\sum_{\boldsymbol{k}}\left(o_{k}-t_{k}\right) \sigma\left(z_{k}\right)\left(1-\sigma\left(z_{k}\right)\right) \frac{\partial z_{k}}{\partial w_{i j}}</script><p>将 $\sigma\left(z<em>{k}\right)$ 写回 $o</em>{k}$ 形式，并利用链式法则，将 $\frac{\partial z<em>{k}}{\partial w</em>{i j}}$ 分解为:</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w_{i j}}=\sum_{k}\left(o_{k}-t_{k}\right) o_{k}\left(1-o_{k}\right) \frac{\partial z_{k}}{\partial o_{j}} \cdot \frac{\partial o_{j}}{\partial w_{i j}}</script><p>中 $\frac{\partial z<em>{k}}{\partial o</em>{j}}=w_{j k}$, 因此:</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w_{i j}}=\sum_{k}\left(o_{k}-t_{k}\right) o_{k}\left(1-o_{k}\right) w_{j k} \frac{\partial o_{j}}{\partial w_{i j}}</script><p>考虑到 $\frac{\partial o<em>{j}}{\partial w</em>{i j}}$ 与 $k$ 无关，可提取公共项为:</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w_{i j}}=\frac{\partial o_{j}}{\partial w_{i j}} \sum_{k}\left(o_{k}-t_{k}\right) o_{k}\left(1-o_{k}\right) w_{j k}</script><p>进一步利用o $<em>{j}=\sigma\left(z</em>{j}\right)$, 并利用 Sigmoid 导数 $\sigma^{\prime}=\sigma(1-\sigma)$, 将 $\frac{\partial o<em>{j}}{\partial w</em>{i j}}$ 拆分为</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w_{i j}}=o_{j}\left(1-o_{j}\right) \frac{\partial z_{j}}{\partial w_{i j}} \sum_{k}\left(o_{k}-t_{k}\right) o_{k}\left(1-o_{k}\right) w_{j k}</script><p>其中 $\frac{\partial z<em>{j}}{\partial w</em>{i j}}$ 的导数可直接推导出为 $o_{i}$, 上式可写为:</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w_{i j}}=o_{j}\left(1-o_{j}\right) o_{i} \sum_{k} \underbrace{\left(o_{k}-t_{k}\right) o_{k}\left(1-o_{k}\right)}_{\delta_{k}^{(K)}} w_{j k}</script><p>其中 $\delta<em>{k}^{(K)}=\left(o</em>{k}-t<em>{k}\right) o</em>{k}\left(1-o<em>{k}\right)$, 则 $\frac{\partial \mathcal{L}}{\partial w</em>{i j}}$ 的表达式可简写为:</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w_{i j}}=o_{j}\left(1-o_{j}\right) o_{i} \sum_{\mathrm{k}} \delta_{k}^{(K)} w_{j k}</script><p>类似地，仿照输出层$\frac{\partial \mathcal{L}}{\partial w<em>{j k}} =\delta</em>{k}^{(K)} x<em>{j}$ 的书写方式，将 $\delta</em>{j}^{J}$ 定义为:</p>
<script type="math/tex; mode=display">
\delta_{j}^{J} \triangleq o_{j}\left(1-o_{j}\right) \sum_{k} \delta_{k}^{(K)} w_{j k}</script><p>此时 $\frac{\partial \mathcal{L}}{\partial w<em>{i j}}$ 可以写为当前连接的起始节点的输出值o $</em>{i}$ 与终止节点j的梯度变量信息 $\delta_{j}^{(J)}$ 的简单相<br>乘运算:</p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w_{i j}}=\delta_{j}^{(J)} o_{i}^{(I)}</script><p><strong>输出层:</strong></p>
<script type="math/tex; mode=display">
\begin{array}{c}
\frac{\partial \mathcal{L}}{\partial w_{j k}}=\delta_{k}^{(K)} o_{j} \\
\delta_{k}^{(K)}=o_{k}\left(1-o_{k}\right)\left(o_{k}-t_{k}\right)
\end{array}</script><p><strong>倒数第二层:</strong></p>
<script type="math/tex; mode=display">
\frac{\partial \mathcal{L}}{\partial w_{i j}}=\delta_{j}^{(J)} o_{i}</script><script type="math/tex; mode=display">
\delta_{j}^{(J)}=o_{j}\left(1-o_{j}\right) \sum_{k} \delta_{k}^{(K)} w_{j k}</script><p><strong>倒数第三层:</strong></p>
<script type="math/tex; mode=display">
\begin{array}{c}
\frac{\partial \mathcal{L}}{\partial w_{n i}}=\delta_{i}^{(I)} o_{n} \\
\delta_{i}^{(I)}=o_{i}\left(1-o_{i}\right) \sum_{j} \delta_{j}^{(J)} w_{i j}
\end{array}</script><p>其中 $o_{n}$ 为倒数第三层的输入，即倒数第四层的输出。</p>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="https://gitee.com/zwyywz/zwyywz.git">Zhouwy</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://gitee.com/zwyywz/zwyywz.git/2020/04/01/DeepLearningAndTensorFlow/">https://gitee.com/zwyywz/zwyywz.git/2020/04/01/DeepLearningAndTensorFlow/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://gitee.com/zwyywz/zwyywz.git" target="_blank">啊粥啊周舟の部落阁</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/zwyywz/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/">机器学习</a><a class="post-meta__tags" href="/zwyywz/tags/TensorFlow/">TensorFlow</a><a class="post-meta__tags" href="/zwyywz/tags/Python/">Python</a><a class="post-meta__tags" href="/zwyywz/tags/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/">深度学习</a></div><div class="post_share"><div class="social-share" data-image="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/deep_learning.png" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><div class="post-reward"><div class="reward-button"><i class="fas fa-qrcode"></i> 打赏</div><div class="reward-main"><ul class="reward-all"><li class="reward-item"><a href="/zwyywz/img/wechat.jpg" target="_blank"><img class="post-qr-code-img" src="/zwyywz/img/wechat.jpg" alt="微信"/></a><div class="post-qr-code-desc">微信</div></li><li class="reward-item"><a href="/zwyywz/img/alipay.jpg" target="_blank"><img class="post-qr-code-img" src="/zwyywz/img/alipay.jpg" alt="支付宝"/></a><div class="post-qr-code-desc">支付宝</div></li></ul></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/zwyywz/2020/04/26/DigitalImageProcessing/" title="数字图像处理（DIP）"><img class="cover" src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/DIP.png" onerror="onerror=null;src='/zwyywz/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">数字图像处理（DIP）</div></div></a></div><div class="next-post pull-right"><a href="/zwyywz/2020/03/31/MathematicaOfMachineLearning/" title="机器学习中的数学原理"><img class="cover" src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/maths.jpg" onerror="onerror=null;src='/zwyywz/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">机器学习中的数学原理</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/zwyywz/2022/06/15/%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E5%88%86%E7%B1%BB%E5%9B%BE%E7%89%87/" title="机器视觉的算法研究"><img class="cover" src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/t.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-06-15</div><div class="title">机器视觉的算法研究</div></div></a></div><div><a href="/zwyywz/2020/03/31/MathematicaOfMachineLearning/" title="机器学习中的数学原理"><img class="cover" src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/maths.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-03-31</div><div class="title">机器学习中的数学原理</div></div></a></div><div><a href="/zwyywz/2021/05/21/%E6%B2%B9%E8%80%97%E9%A2%84%E6%B5%8B/" title="汽车油耗预测实战"><img class="cover" src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/油耗.jpg" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2021-05-21</div><div class="title">汽车油耗预测实战</div></div></a></div><div><a href="/zwyywz/2020/04/26/DigitalImageProcessing/" title="数字图像处理（DIP）"><img class="cover" src="https://blog-1300216920.cos.ap-nanjing.myqcloud.com/DIP.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2020-04-26</div><div class="title">数字图像处理（DIP）</div></div></a></div></div></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E4%B8%8ETensorFlow"><span class="toc-number">1.</span> <span class="toc-text">深度学习与TensorFlow</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%AC%AC%E4%B8%80%E7%AB%A0-%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92"><span class="toc-number">1.1.</span> <span class="toc-text">第一章 线性回归</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1%E3%80%81%E7%A5%9E%E7%BB%8F%E5%85%83%E6%A8%A1%E5%9E%8B"><span class="toc-number">1.1.1.</span> <span class="toc-text">1、神经元模型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2%E3%80%81%E6%A2%AF%E5%BA%A6-Gradient-%EF%BC%9A"><span class="toc-number">1.1.2.</span> <span class="toc-text">2、梯度(Gradient)：</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3%E3%80%81%E7%BA%BF%E6%80%A7%E5%9B%9E%E5%BD%92-tensorflow%E5%AE%9E%E6%88%98"><span class="toc-number">1.1.3.</span> <span class="toc-text">3、线性回归-tensorflow实战</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%AC%AC%E4%BA%8C%E7%AB%A0-%E5%88%86%E7%B1%BB%E9%97%AE%E9%A2%98"><span class="toc-number">1.2.</span> <span class="toc-text">第二章 分类问题</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#2-1-%E6%89%8B%E5%86%99%E6%95%B0%E5%AD%97%E5%9B%BE%E7%89%87%E6%95%B0%E6%8D%AE%E9%9B%86"><span class="toc-number">1.2.1.</span> <span class="toc-text">2.1 手写数字图片数据集</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-2-%E6%A8%A1%E5%9E%8B%E6%9E%84%E5%BB%BA"><span class="toc-number">1.2.2.</span> <span class="toc-text">2.2 模型构建</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-3-%E8%AF%AF%E5%B7%AE%E8%AE%A1%E7%AE%97"><span class="toc-number">1.2.3.</span> <span class="toc-text">2.3 误差计算</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-4-%E9%9D%9E%E7%BA%BF%E6%80%A7%E6%A8%A1%E5%9E%8B"><span class="toc-number">1.2.4.</span> <span class="toc-text">2.4 非线性模型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-5-%E8%A1%A8%E8%BE%BE%E8%83%BD%E5%8A%9B"><span class="toc-number">1.2.5.</span> <span class="toc-text">2.5 表达能力</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-6-%E6%89%8B%E5%86%99%E6%95%B0%E5%AD%97%E5%9B%BE%E7%89%87%E8%AF%86%E5%88%AB-Tensorflow%E5%AE%9E%E6%88%98"><span class="toc-number">1.2.6.</span> <span class="toc-text">2.6 手写数字图片识别-Tensorflow实战</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%AC%AC%E4%B8%89%E7%AB%A0-TensorFlow%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80"><span class="toc-number">1.3.</span> <span class="toc-text">第三章 TensorFlow编程基础</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#3-1-%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B"><span class="toc-number">1.3.1.</span> <span class="toc-text">3.1 数据类型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-2-%E6%95%B0%E5%80%BC%E7%B2%BE%E5%BA%A6"><span class="toc-number">1.3.2.</span> <span class="toc-text">3.2 数值精度</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-3-%E5%BE%85%E4%BC%98%E5%8C%96%E5%BC%A0%E9%87%8F"><span class="toc-number">1.3.3.</span> <span class="toc-text">3.3 待优化张量</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-4-%E5%88%9B%E5%BB%BA%E5%BC%A0%E9%87%8F"><span class="toc-number">1.3.4.</span> <span class="toc-text">3.4 创建张量</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-5-%E5%BC%A0%E9%87%8F%E7%9A%84%E5%85%B8%E5%9E%8B%E5%BA%94%E7%94%A8"><span class="toc-number">1.3.5.</span> <span class="toc-text">3.5 张量的典型应用</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-6-%E7%B4%A2%E5%BC%95%E4%B8%8E%E5%88%87%E7%89%87"><span class="toc-number">1.3.6.</span> <span class="toc-text">3.6  索引与切片</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%AC%AC%E5%9B%9B%E7%AB%A0-%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C"><span class="toc-number">1.4.</span> <span class="toc-text">第四章 神经网络</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#4-1-%E5%85%A8%E8%BF%9E%E6%8E%A5%E5%B1%82"><span class="toc-number">1.4.1.</span> <span class="toc-text">4.1 全连接层</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-2-%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C"><span class="toc-number">1.4.2.</span> <span class="toc-text">4.2 神经网络</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-3-%E4%BC%98%E5%8C%96%E7%9B%AE%E6%A0%87"><span class="toc-number">1.4.3.</span> <span class="toc-text">4.3 优化目标</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-4-%E6%BF%80%E6%B4%BB%E5%87%BD%E6%95%B0"><span class="toc-number">1.4.4.</span> <span class="toc-text">4.4 激活函数</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-5-%E8%BE%93%E5%87%BA%E5%B1%82%E8%AE%BE%E8%AE%A1"><span class="toc-number">1.4.5.</span> <span class="toc-text">4.5 输出层设计</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-6-%E8%AF%AF%E5%B7%AE%E8%AE%A1%E7%AE%97"><span class="toc-number">1.4.6.</span> <span class="toc-text">4.6 误差计算</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%AC%AC%E4%BA%94%E7%AB%A0-%E5%8F%8D%E5%90%91%E4%BC%A0%E6%92%AD%E7%AE%97%E6%B3%95"><span class="toc-number">1.5.</span> <span class="toc-text">第五章 反向传播算法</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#5-1-%E5%AF%BC%E6%95%B0%E4%B8%8E%E6%A2%AF%E5%BA%A6"><span class="toc-number">1.5.1.</span> <span class="toc-text">5.1 导数与梯度</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-2-%E6%BF%80%E6%B4%BB%E5%87%BD%E6%95%B0%E5%AF%BC%E6%95%B0"><span class="toc-number">1.5.2.</span> <span class="toc-text">5.2 激活函数导数</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-3-%E6%8D%9F%E5%A4%B1%E5%87%BD%E6%95%B0%E6%A2%AF%E5%BA%A6"><span class="toc-number">1.5.3.</span> <span class="toc-text">5.3 损失函数梯度</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-4-%E5%85%A8%E8%BF%9E%E6%8E%A5%E5%B1%82%E6%A2%AF%E5%BA%A6"><span class="toc-number">1.5.4.</span> <span class="toc-text">5.4 全连接层梯度</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-5-%E5%8F%8D%E5%90%91%E4%BC%A0%E6%92%AD%E7%AE%97%E6%B3%95"><span class="toc-number">1.5.5.</span> <span class="toc-text">5.5 反向传播算法</span></a></li></ol></li></ol></li></ol></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2020 - 2023 By Zhouwy</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">简</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/zwyywz/js/utils.js"></script><script src="/zwyywz/js/main.js"></script><script src="/zwyywz/js/tw_cn.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox/fancybox.umd.min.js"></script><div class="js-pjax"><script>if (!window.MathJax) {
  window.MathJax = {
    tex: {
      inlineMath: [ ['$','$'], ["\\(","\\)"]],
      tags: 'ams'
    },
    chtml: {
      scale: 1.1
    },
    options: {
      renderActions: {
        findScript: [10, doc => {
          for (const node of document.querySelectorAll('script[type^="math/tex"]')) {
            const display = !!node.type.match(/; *mode=display/)
            const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display)
            const text = document.createTextNode('')
            node.parentNode.replaceChild(text, node)
            math.start = {node: text, delim: '', n: 0}
            math.end = {node: text, delim: '', n: 0}
            doc.math.push(math)
          }
        }, ''],
        insertScript: [200, () => {
          document.querySelectorAll('mjx-container').forEach(node => {
            if (node.hasAttribute('display')) {
              btf.wrap(node, 'div', { class: 'mathjax-overflow' })
            } else {
              btf.wrap(node, 'span', { class: 'mathjax-overflow' })
            }
          });
        }, '', false]
      }
    }
  }
  
  const script = document.createElement('script')
  script.src = 'https://cdn.jsdelivr.net/npm/mathjax/es5/tex-mml-chtml.min.js'
  script.id = 'MathJax-script'
  script.async = true
  document.head.appendChild(script)
} else {
  MathJax.startup.document.state(0)
  MathJax.texReset()
  MathJax.typesetPromise()
}</script></div><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>